Lucene的索引文件格式(2)
上面曾经交代过,Lucene保存了从Index到Segment到Document到Field一直到Term的正向信息,也包括了从Term到Document映射的反向信息,还有其他一些Lucene特有的信息。下面对这三种信息一一介绍。
Index –> Segments (segments.gen, segments_N) –> Field(fnm, fdx, fdt) –> Term (tvx, tvd, tvf)
上面的层次结构不是十分的准确,因为segments.gen和segments_N保存的是段(segment)的元数据信息(metadata),其实是每个Index一个的,而段的真正的数据信息,是保存在域(Field)和词(Term)中的。
一个索引(Index)可以同时存在多个segments_N(至于如何存在多个segments_N,在描述完详细信息之后会举例说明),然而当我们要打开一个索引的时候,我们必须要选择一个来打开,那如何选择哪个segments_N呢?
Lucene采取以下过程:
IndexInput genInput = directory.openInput(IndexFileNames.SEGMENTS_GEN);//"segments.gen"?
int version = genInput.readInt();//读出版本号?
if (version == FORMAT_LOCKLESS) {//如果版本号正确?
??? long gen0 = genInput.readLong();//读出第一个N?
??? long gen1 = genInput.readLong();//读出第二个N?
??? if (gen0 == gen1) {//如果两者相等则为genB?
??????? genB = gen0;?
??? }?
}
if (genA > genB)?
??? gen = genA;?
else?
??? gen = genB;
?
如下图是segments_N的具体格式:
//在DirectoryReader中有一下函数。
public boolean isCurrent() throws CorruptIndexException, IOException {?
? return SegmentInfos.readCurrentVersion(directory) == segmentInfos.getVersion();?
}
IndexWriter writer = new IndexWriter(FSDirectory.open(INDEX_DIR), new StandardAnalyzer(Version.LUCENE_CURRENT), true, IndexWriter.MaxFieldLength.LIMITED);?
writer.setUseCompoundFile(false);?
indexDocs(writer, docDir);//docDir中只有两篇文档
//文档一为:Students should be allowed to go out with their friends, but not allowed to drink beer.
//文档二为:My friend Jerry went to school to see his students but found them drunk which is not allowed.
writer.commit();//提交两篇文档,形成_0段。
writer.deleteDocuments(new Term("contents", "school"));//删除文档二?
writer.commit();//提交删除,形成_0_1.del?
indexDocs(writer, docDir);//再次索引两篇文档,Lucene不能判别文档与文档的不同,因而算两篇新的文档。?
writer.commit();//提交两篇文档,形成_1段?
writer.deleteDocuments(new Term("contents", "school"));//删除第二次添加的文档二?
writer.close();//提交删除,形成_1_1.del
IndexWriter.applyDeletes()
-> DocumentsWriter.applyDeletes(SegmentInfos)
???? -> reader.deleteDocument(doc);
IndexWriter.commit()
-> IndexWriter.applyDeletes()
??? -> IndexWriter$ReaderPool.release(SegmentReader)
???????? -> SegmentReader(IndexReader).commit()
???????????? -> SegmentReader.doCommit(Map)
????????????????? -> SegmentInfo.advanceDelGen()
?????????????????????? -> if (delGen == NO) {?
????????????????????????????? delGen = YES;?
?????????????????????????? } else {?
????????????????????????????? delGen++;?
?????????????????????????? }
IndexWriter writer = new IndexWriter(FSDirectory.open(INDEX_DIR), new StandardAnalyzer(Version.LUCENE_CURRENT), true, IndexWriter.MaxFieldLength.LIMITED);?
writer.setUseCompoundFile(false);
indexDocs(writer, docDir);//索引两篇文档,一篇包含"school",另一篇包含"beer"?
writer.commit();//提交两篇文档到索引文件,形成段(Segment) "_0"?
writer.deleteDocuments(new Term("contents", "school"));//删除包含"school"的文档,其实是删除了两篇文档中的一篇。?
writer.commit();//提交删除到索引文件,形成"_0_1.del"?
writer.deleteDocuments(new Term("contents", "beer"));//删除包含"beer"的文档,其实是删除了两篇文档中的另一篇。?
writer.commit();//提交删除到索引文件,形成"_0_2.del"?
indexDocs(writer, docDir);//索引两篇文档,和上次的文档相同,但是Lucene无法区分,认为是另外两篇文档。?
writer.commit();//提交两篇文档到索引文件,形成段"_1"?
writer.deleteDocuments(new Term("contents", "beer"));//删除包含"beer"的文档,其中段"_0"已经无可删除,段"_1"被删除一篇。?
writer.close();//提交删除到索引文件,形成"_1_1.del"
形成的索引文件如下:
?
????? IndexWriter writer = new IndexWriter(FSDirectory.open(INDEX_DIR), new StandardAnalyzer(Version.LUCENE_CURRENT), true, IndexWriter.MaxFieldLength.LIMITED);?
????? writer.setUseCompoundFile(false);
????
????? indexDocs(writer, docDir);?
????? writer.flush();
//flush生成segment "_0",并且flush函数中,flushDocStores设为false,也即下个段将同本段共享域和词向量信息,这时DocumentsWriter中的docStoreSegment= "_0"。
????? indexDocs(writer, docDir);?
????? writer.commit();
//commit生成segment "_1",由于上次flushDocStores设为false,于是段"_1"的域以及词向量信息是保存在"_0"中的,在这个时刻,段"_1"并不生成自己的"_1.fdx"和"_1.fdt"。然而在commit函数中,flushDocStores设为true,也即下个段将单独使用新的段来存储域和词向量信息。然而这时,DocumentsWriter中的docStoreSegment= "_1",也即当段"_2"存储其域和词向量信息的时候,是存在"_1.fdx"和"_1.fdt"中的,而段"_1"的域和词向量信息却是存在"_0.fdt"和"_0.fdx"中的,这一点非常令人困惑。 如图writer.commit的时候,_1.fdt和_1.fdx并没有形成。
????? indexDocs(writer, docDir);?
????? writer.flush();
//段"_2"形成,由于上次flushDocStores设为true,其域和词向量信息是新创建一个段保存的,却是保存在_1.fdt和_1.fdx中的,这时候才产生了此二文件。
????? indexDocs(writer, docDir);?
????? writer.flush();
//段"_3"形成,由于上次flushDocStores设为false,其域和词向量信息是共享一个段保存的,也是是保存在_1.fdt和_1.fdx中的
????? indexDocs(writer, docDir);?
????? writer.commit();
//段"_4"形成,由于上次flushDocStores设为false,其域和词向量信息是共享一个段保存的,也是是保存在_1.fdt和_1.fdx中的。然而函数commit中flushDocStores设为true,也意味着下一个段将新创建一个段保存域和词向量信息,此时DocumentsWriter中docStoreSegment= "_4",也表明了虽然段"_4"的域和词向量信息保存在了段"_1"中,将来的域和词向量信息却要保存在段"_4"中。此时"_4.fdx"和"_4.fdt"尚未产生。???
????? indexDocs(writer, docDir);?
????? writer.flush();
//段"_5"形成,由于上次flushDocStores设为true,其域和词向量信息是新创建一个段保存的,却是保存在_4.fdt和_4.fdx中的,这时候才产生了此二文件。
????? indexDocs(writer, docDir);?
????? writer.commit();?
????? writer.close();
//段"_6"形成,由于上次flushDocStores设为false,其域和词向量信息是共享一个段保存的,也是是保存在_4.fdt和_4.fdx中的
?
读取此文件格式参考SegmentInfos.read(Directory directory, String segmentFileName):
?
一个段(Segment)包含多个域,每个域都有一些元数据信息,保存在.fnm文件中,.fnm文件的格式如下:
要了解域的元数据信息,还要了解以下几点:
public static final String ID_PAYLOAD_FIELD = "_ID";
public static final String ID_PAYLOAD_TERM = "_ID";
public static final Term ID_TERM = new Term(ID_PAYLOAD_TERM, ID_PAYLOAD_FIELD);
//声明一个特殊的TokenStream,它只生成一个词(Term),就是那个特殊的词,在特殊的域里面。
static class SinglePayloadTokenStream extends TokenStream {?
??? private Token token;?
??? private boolean returnToken = false;
??? SinglePayloadTokenStream(String idPayloadTerm) {?
??????? char[] term = idPayloadTerm.toCharArray();?
??????? token = new Token(term, 0, term.length, 0, term.length);?
??? }
??? void setPayloadValue(byte[] value) {?
??????? token.setPayload(new Payload(value));?
??????? returnToken = true;?
??? }
??? public Token next() throws IOException {?
??????? if (returnToken) {?
??????????? returnToken = false;?
??????????? return token;?
??????? } else {?
??????????? return null;?
??????? }?
??? }?
}
//对于每一篇文档,都让它包含这个特殊的词,在特殊的域里面
SinglePayloadTokenStream singlePayloadTokenStream = new SinglePayloadTokenStream(ID_PAYLOAD_TERM);?
singlePayloadTokenStream.setPayloadValue(long2bytes(id));?
doc.add(new Field(ID_PAYLOAD_FIELD, singlePayloadTokenStream));
long id = 0;?
TermPositions tp = reader.termPositions(ID_PAYLOAD_TERM);?
boolean ret = tp.skipTo(docID);?
tp.nextPosition();?
int payloadlength = tp.getPayloadLength();?
byte[] payloadBuffer = new byte[payloadlength];?
tp.getPayload(payloadBuffer, 0);?
id = bytes2long(payloadBuffer);?
tp.close();
?
FieldInfos.read(IndexInput, String)
?
Document FieldsReader.doc(int n, FieldSelector fieldSelector)
词向量信息是从索引(index)到文档(document)到域(field)到词(term)的正向信息,有了词向量信息,我们就可以得到一篇文档包含那些词的信息。
TermVectorsReader.get(int docNum, String field, TermVectorMapper)