Lucene索引文件解析之“域”
http://zhongl.iteye.com/blog/291127
引言
目前最新的Lucene的版本是2.4.0,但关于索引文件格式(Index File Format)的说明并未完全及时更新,所以后文是基于版本2.1.0展开的解析。解析内容并未涉及全面,更多详细准确的说明还请参见[1]。
在看下文之前,若熟悉Lucene的索引的基本概念和过程就会对后文的理解有很大帮助。
创建一个简单的索引
这里有一段代码,它将创建一个简单的索引段(Segment),这个段只有一个文档(Document),文档中有两个域(Field):name和description。两个域均进行索引(Index)并保存(Store),区别在于后者需要分词(Tokenized),代码如下:
- public class App {
- public static void main(String[] args) {
- Document doc = new Document();
- Field[] fields = createFields();
- for (int i = 0; i < fields.length; i++) {
- doc.add(fields[i]);
- }
- createIndex(doc);
- }
- private static void createIndex(Document doc) {
- try {
- boolean create = true;
- IndexWriter writer = new IndexWriter("index",
- new StandardAnalyzer(), create);
- writer.setUseCompoundFile(false);
- writer.addDocument(doc);
- writer.optimize();
- writer.close();
- } catch (IOException e) {
- // 异常处理
- }
- }
- private static Field[] createFields() {
- Field name = new Field("name", "allen", Field.Store.YES,
- Field.Index.UN_TOKENIZED);
- Field desc = new Field("description", "i'm a new coder for lucene.",
- Field.Store.YES, Field.Index.TOKENIZED);
- return new Field[] { name, desc };
- }
- }
注意上面代码中,有:
这里避免了1.4本版之后就默认采用合成模式,而是采用传统模式,这是为了方便索引文件格式的分析,二者原理是一致的。
传统模式下产生的索引文件将会是:
2008-12-11 17:12 41 segments_6
2008-12-11 17:12 39 _2.fdt
2008-12-11 17:12 8 _2.fdx
2008-12-11 17:12 20 _2.fnm
2008-12-11 17:12 5 _2.frq
2008-12-11 17:12 6 _2.nrm
2008-12-11 17:12 5 _2.prx
2008-12-11 17:12 31 _2.tii
2008-12-11 17:12 72 _2.tis
注意,这里出现的文件名前缀若不是“_2”也没有关系。
关于域
关于域会涉及上面三个文件,第一个是.fnm,它的作用是保存所有的域名的信息,其文件的十六进制内容如下:
- 02 表示所有域的个数为2;
- 0B 表示第一个域名的字符串长度为11;
- 64 65 73 63 72 69 70 74 69 6F 6E 是“description”的UTF-8的编码;
- 01 表示第一个域是创建了索引的,这里应转换成二进制为按位的0/1值来判断索引配置信息(详见[1])。
剩余的部分是另一域“name”的数据。由此可见,fnm文件的格式为:<域个数>[<域名长度><域名字符串><域的索引配置> ...]。
fnm中域名是根据字母表进行排序了的,这是很重要的,后文的项字典(Term Dictionary)中项(Term)的顺序就是按照它对应的域顺序来排的。
fnm的内容可以转换下面比较容易理解的表格:
Field Name | Indexed? | Vectored? | Positions Stored? | Offsets Stored? | Norms Omitted? | Payloads Stored? |
description | √ | - | - | - | - | - |
name | √ | - | - | - | - | - |
第二文件是.fdx,它的作用是记录每个文档所有存储的域数据(Field Data)在.fdt文件中的开始位置,其文件的十六进制内容如下:
8个字节的0似乎没有给我们任何信息,这是由于创建索引的代码中只添加了一个文档,显然这里已经表示了文档的开始位置为0。为了让解析更有说服力,此时将上述代码做如下调整:
- boolean create = false;// 之前是true,这里设为false代表IndexWriter将在已存在的文件中添加内容
- private static Field[] createFields() {
- Field name = new Field("name", "rocky", Field.Store.YES,
- Field.Index.UN_TOKENIZED);
- Field desc = new Field("description", "a expert of lucene.",
- Field.Store.YES, Field.Index.TOKENIZED);
- return new Field[] { name, desc };
- }// 修改数据,以添加一个新的文档
再次运行代码,便会在索引中添加一个新的文档。此时,再看.fdx文件内容(文件名前缀发生了变化):
显而易见,内容由两个8个字节的十六进制数组成,它们分别代表两个文档的域数据在.fdt中的开始位置,第一文档是0,第二个文档是27*8。那么,fdx的文件格式很简单:[<文档域值的开始位置>...]。
来看看.fdt吧,里面记录着定义要存储的域数据:
61 20 6E 65 77 20 63 6F 64 65 72 20 66 6F 72 20 a new coder for
6C 75 63 65 6E 65 2E 02 01 00 05 72 6F 63 6B 79 lucene.....rocky
00 01 13 61 20 65 78 70 65 72 74 20 6F 66 20 6C ...a expert of l
75 63 65 6E 65 2E ucene.
- 02 表示记录了域数据的文档个数为2;
- 01 表示域的序号为1,也就是后面的数据是name域的值(见fnm中name是排在第二位的);
- 00 表示后面数据没有经过分词,是字符串,且没有进行压缩处理,这里应转换成二进制为按位的0/1值来判断数据的状态的(详见[1]),数据也有可能是BinaryValue;
- 05 表示字符串的长度;
- 61 6C 6C 65 6E 表述字符串“allen”。
余下的数据依此类推。这样,fdt的格式也很清晰了:<文档个数>[<域序号><域状态><域数据长度><域数据>...]
未完待续,下一部分聊“项”...
参考资料
[1] Apache Lucene - Index File Formats
[3] 深入 Lucene 索引机制
[4] " Lucene In Action" by Erik Hatcher, Otis Gospodnetić; Manning Publications; December 2004; ISBN 1932394281