引言
目前最新的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 7 2 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