如果在初始化一個(gè)IndexWriter索引器的時(shí)候,指定 useCompoundFile =false,則在指定的索引目錄中生成的索引文件就不是.cfs復(fù)合索引文件。
通過(guò)這種方式生成的索引文件,它的不同格式表明了它鎖存儲(chǔ)的關(guān)于索引的不同內(nèi)容。
至少,明確了在建立索引過(guò)程中,經(jīng)過(guò)加工處理的數(shù)據(jù)究竟去向如何,能夠加深對(duì)Lucene索引過(guò)程的理解。
通過(guò)在文章 Lucene-2.2.0 源代碼閱讀學(xué)習(xí)(4) 中的那個(gè)例子,可以運(yùn)行主函數(shù),觀察到索引目錄中生成了大量的不同擴(kuò)展名的索引文件,當(dāng)然它們不是復(fù)合索引文件,如圖所示:
這些不同擴(kuò)展名的索引文件都是有一定的含義的。
如果只是根據(jù)這些文件名來(lái)說(shuō)明它的含義,讓人感覺(jué)很抽象,那么就通過(guò)代碼來(lái)看,它們到底都存儲(chǔ)了一些什么內(nèi)容。
_N.fnm文件
當(dāng)向一個(gè)IndexWriter索引器實(shí)例添加Document的時(shí)候,調(diào)用了IndexWroter的addDocument()方法,在方法的內(nèi)部調(diào)用如下:
buildSingleDocSegment() —> String segmentName = newRamSegmentName();
這時(shí),調(diào)用newRamSegmentName()方法生成了一個(gè)segment的名稱,形如_ram_N,這里N為36進(jìn)制數(shù)。
這個(gè)新生成的segmentName作為參數(shù)值傳遞到DocumentWriter類的addDocument()方法中:
dw.addDocument(segmentName, doc);
在DocumentWriter類中,這個(gè)segmentName依然是_ram_N形式的,再次作為參數(shù)值傳遞:
fieldInfos.write(directory, segment + ".fnm");
這個(gè)時(shí)候,就要發(fā)生變化了,在FieldInfos類的第一個(gè)write()方法中輸出System.out.println(name);,結(jié)果如下所示:
_ram_0.fnm
_ram_1.fnm
_ram_2.fnm
_ram_3.fnm
_ram_4.fnm
_ram_5.fnm
_ram_6.fnm
_ram_7.fnm
_ram_8.fnm
_ram_9.fnm
_0.fnm
_ram_a.fnm
_ram_b.fnm
_ram_c.fnm
_ram_d.fnm
_ram_e.fnm
_ram_f.fnm
_ram_g.fnm
_ram_h.fnm
_ram_i.fnm
_ram_j.fnm
_1.fnm
_ram_k.fnm
……
而且,可以從Directory看出究竟在這個(gè)過(guò)程中發(fā)生了怎樣的切換過(guò)程,在FieldInfos類的第一個(gè)write()方法中執(zhí)行:
??? if(d instanceof FSDirectory){
??? System.out.println("FSDirectory");
??? }
??? else{
??? System.out.println("----RAMDirectory");
??? }
輸出結(jié)果如下所示:
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
FSDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
----RAMDirectory
FSDirectory
……
可以看出,每次處理過(guò)10個(gè).fnm文件(文件全名_ram_N.fnm),是在RAMDirectory中,然后就切換到FSDirectory中,這時(shí)輸出到本地磁盤(pán)的索引目錄中的索引文件是_N.fnm,可以從上面的實(shí)例圖中看到_0.fnm、_1.fnm等等。
真正執(zhí)行向_N.fnm文件中寫(xiě)入內(nèi)容是在FieldInfos類的第二個(gè)write()方法中,可以從該方法的實(shí)現(xiàn)來(lái)看到底都寫(xiě)入了哪些內(nèi)容:
public void write(IndexOutput output) throws IOException {
??? output.writeVInt(size());
??? for (int i = 0; i < size(); i++) {
????? FieldInfo fi = fieldInfo(i);
????? byte bits = 0x0;
????? if (fi.isIndexed) bits |= IS_INDEXED;
????? if (fi.storeTermVector) bits |= STORE_TERMVECTOR;
????? if (fi.storePositionWithTermVector) bits |= STORE_POSITIONS_WITH_TERMVECTOR;
????? if (fi.storeOffsetWithTermVector) bits |= STORE_OFFSET_WITH_TERMVECTOR;
????? if (fi.omitNorms) bits |= OMIT_NORMS;
????? if (fi.storePayloads) bits |= STORE_PAYLOADS;
????? output.writeString(fi.name);
????? output.writeByte(bits);
??? }
}
從后兩行代碼可以看出,首先寫(xiě)入了一個(gè)Field的名稱(name),然后寫(xiě)入了一個(gè)byte值。這個(gè)byte的值可以根據(jù)從該FieldInfos類定義的一些標(biāo)志經(jīng)過(guò)位運(yùn)算得到,從而從FieldIno的實(shí)例中讀取Field的信息,根據(jù)Field的一些信息(如:是否被索引、是否存儲(chǔ)詞條向量等等)來(lái)設(shè)置byte bits,這些標(biāo)志的定義為:
static final byte IS_INDEXED = 0x1;
static final byte STORE_TERMVECTOR = 0x2;
static final byte STORE_POSITIONS_WITH_TERMVECTOR = 0x4;
static final byte STORE_OFFSET_WITH_TERMVECTOR = 0x8;
static final byte OMIT_NORMS = 0x10;
static final byte STORE_PAYLOADS = 0x20;
_N.fdt文件和_N.fdx文件
接著,在DocumentWriter類中的addDocumet()方法中,根據(jù)Directory實(shí)例、segment的名稱、一個(gè)FieldInfos的實(shí)例構(gòu)造了一個(gè)FieldsWriter類的實(shí)例:
FieldsWriter fieldsWriter =?? new FieldsWriter(directory, segment, fieldInfos);
可以從FieldsWriter類的構(gòu)造方法可以看出,實(shí)際上,根據(jù)生成的segment的名稱(_ram_N和_N)創(chuàng)建了兩個(gè)輸出流對(duì)象:
??? FieldsWriter(Directory d, String segment, FieldInfos fn) throws IOException {
??????? fieldInfos = fn;????????
???????
fieldsStream
= d.createOutput(segment + ".fdt");
???????
indexStream
= d.createOutput(segment + ".fdx");
??? }
這時(shí),_N.fdt和_N.fdx文件就要生成了。
繼續(xù)看DocumentWriter類中的addDocument()方法:
fieldsWriter.addDocument(doc);
這時(shí)進(jìn)入到FieldsWriter類中了,在addDocument()方法中提取Field的信息,寫(xiě)入到,_N.fdt和_N.fdx文件中。FieldsWriter類的addDocument()方法實(shí)現(xiàn)如下:
??? final void addDocument(Document doc) throws IOException {
???????
indexStream.writeLong(fieldsStream.getFilePointer());
??
// 向indexStream中(即_N.fdx文件)中寫(xiě)入fieldsStream(_N.fdt文件)流中的當(dāng)前位置,也就是寫(xiě)入這個(gè)Field信息的位置
??????? int storedCount = 0;
??????? Iterator fieldIterator = doc.getFields().iterator();
??????? while (fieldIterator.hasNext()) {??
// 循環(huán)遍歷該Document中所有Field,統(tǒng)計(jì)需要存儲(chǔ)的Field的個(gè)數(shù)
??????????? Fieldable field = (Fieldable) fieldIterator.next();
??????????? if (field.isStored())
??????????????? storedCount++;
??????? }
??????
fieldsStream.writeVInt(storedCount);
??
// 存儲(chǔ)Document中需要存儲(chǔ)的的Field的個(gè)數(shù),寫(xiě)入到_N.fdt文件
??????? fieldIterator = doc.getFields().iterator();
??????? while (fieldIterator.hasNext()) {
??????????? Fieldable field = (Fieldable) fieldIterator.next();
??????????? // if the field as an instanceof FieldsReader.FieldForMerge, we're in merge mode
??????????? // and field.binaryValue() already returns the compressed value for a field
??????????? // with isCompressed()==true, so we disable compression in that case
??????????? boolean disableCompression = (field instanceof FieldsReader.FieldForMerge);
??????????? if (field.isStored()) {???
// 如果Field需要存儲(chǔ),將該Field的編號(hào)寫(xiě)入到_N.fdt文件
???????????????
fieldsStream.writeVInt(fieldInfos.fieldNumber(field.name()));
??????????????? byte bits = 0;
??????????????? if (field.isTokenized())
??????????????????? bits |= FieldsWriter.FIELD_IS_TOKENIZED;
??????????????? if (field.isBinary())
??????????????????? bits |= FieldsWriter.FIELD_IS_BINARY;
??????????????? if (field.isCompressed())
??????????????????? bits |= FieldsWriter.FIELD_IS_COMPRESSED;
???????????????
???????????????
fieldsStream.writeByte(bits);
??
// 將Field的是否分詞,或是否壓縮,或是否以二進(jìn)制流存儲(chǔ),這些信息都寫(xiě)入到_N.fdt文件
????????????????
??????????????? if (field.isCompressed()) {
?????????????????
// 如果當(dāng)前Field可以被壓縮
????????????????? byte[] data = null;
?????????????????
????????????????? if (disableCompression) {
?????????????????????
// 已經(jīng)被壓縮過(guò),科恩那個(gè)需要進(jìn)行合并優(yōu)化
????????????????????? data = field.binaryValue();
????????????????? } else {
?????????????????????
// 檢查Field是否以二進(jìn)制存儲(chǔ)
????????????????????? if (field.isBinary()) {
??????????????????????? data = compress(field.binaryValue());
????????????????????? }
????????????????????? else {????
//?? 設(shè)置編碼方式,壓縮存儲(chǔ)處理
??????????????????????? data = compress(field.stringValue().getBytes("UTF-8"));
????????????????????? }
????????????????? }
????????????????? final int len = data.length;
?????????????????
fieldsStream.writeVInt(len);
???
// 寫(xiě)入Field名稱(以二進(jìn)制存儲(chǔ))的長(zhǎng)度到_N.fdt文件
?????????????????
fieldsStream.writeBytes(data, len);
// 通過(guò)字節(jié)流的方式,寫(xiě)入Field名稱(以二進(jìn)制存儲(chǔ))到_N.fdt文件
??????????????? }
??????????????? else {
?????????????????
// 如果當(dāng)前這個(gè)Field不能進(jìn)行壓縮
????????????????? if (field.isBinary()) {
??????????????????? byte[] data = field.binaryValue();
??????????????????? final int len = data.length;
???????????????????
fieldsStream.writeVInt(len);
??????????????????? fieldsStream.writeBytes(data, len);
????????????????? }
????????????????? else {
???????????????????
fieldsStream.writeString(field.stringValue());
???
// 如果Field不是以二進(jìn)制存儲(chǔ),則以String的格式寫(xiě)入到_N.fdt文件
????????????????? }
??????????????? }
??????????? }
??????? }
??? }
從該方法可以看出:
_N.fdx文件(即indexStream流)中寫(xiě)入的內(nèi)容是:一個(gè)Field在_N.fdt文件中位置。
_N.fdt文件(即fieldsStream流)中寫(xiě)入的內(nèi)容是:
(1) Document中需要存儲(chǔ)的Field的數(shù)量;
(2) 每個(gè)Field在Document中的編號(hào);
(3) 每個(gè)Field關(guān)于是否分詞、是否壓縮、是否以二進(jìn)制存儲(chǔ)這三個(gè)指標(biāo)的一個(gè)組合值;
(4) 每個(gè)Field的長(zhǎng)度;
(5) 每個(gè)Field的內(nèi)容(binaryValue或stringValue);
_N.frq文件和_N.prx文件
仍然在DocumentWriter類的addDocument()方法中看:
writePostings(postings, segment);
因?yàn)樵谡{(diào)用該方法之前,已經(jīng)對(duì)Documeng進(jìn)行了倒排,在倒排的過(guò)程中對(duì)Document中的Field進(jìn)行了處理,如果Field指定了要進(jìn)行分詞,則在倒排的時(shí)候進(jìn)行了分詞處理,這時(shí)生成了詞條。然后調(diào)用writePostings()方法,根據(jù)生成的segment的名稱_ram_N,設(shè)置詞條的頻率、位置等信息,并寫(xiě)入到索引目錄中。
在writePostings()方法中,首先創(chuàng)建了兩個(gè)輸出流:
????? freq = directory.createOutput(segment + ".frq");
????? prox = directory.createOutput(segment + ".prx");
這時(shí),_N.frq文件和_N.prx文件就要在索引目錄中生成了。
經(jīng)過(guò)倒排,各個(gè)詞條的重要信息都被存儲(chǔ)到了Posting對(duì)象中,Posting類是為詞條的信息服務(wù)的。因此,在writePostings()方法中可以遍歷Posting[]數(shù)組中的各個(gè)Posting實(shí)例,讀取并處理這些信息,然后輸出到索引目錄中。
設(shè)置_N.frq文件的起始寫(xiě)入內(nèi)容:
??????? int postingFreq = posting.freq;
??????? if (postingFreq == 1)?????
// 如果該詞條第一次出現(xiàn)造Document中
????????? freq.writeVInt(1);????
// 頻率色繪制為1
??????? else {
????????? freq.writeVInt(0);????
// 如果不是第一次出現(xiàn),對(duì)應(yīng)的Document的編號(hào)0要寫(xiě)入到_N.frq文件
????????? freq.writeVInt(postingFreq);????
// 設(shè)置一個(gè)詞條在該Document中的頻率值
??????? }
再看prox輸出流:
??????????? if (payloadLength == lastPayloadLength) {????
// 其中,int lastPayloadLength = -1;
?????????????
// the length of the current payload equals the length
??????????? // of the previous one. So we do not have to store the length
??????????? // again and we only shift the position delta by one bit
?????????????
prox.writeVInt(delta * 2);
???
//其中,int delta = position - lastPosition,int position = positions[j];
??????????? } else {
???????????
// the length of the current payload is different from the
??????????? // previous one. We shift the position delta, set the lowest
??????????? // bit and store the current payload length as VInt.
????????????
prox.writeVInt(delta * 2 + 1);
????????????? prox.writeVInt(payloadLength);
????????????? lastPayloadLength = payloadLength;
??????????? }
??????????? if (payloadLength > 0) {
???????????
// write current payload
?????????????
prox.writeBytes(payload.data, payload.offset, payload.length);
??????????? }
????????? } else {
?????????
// field does not store payloads, just write position delta as VInt
???????????
prox.writeVInt(delta);
????????? }
一個(gè)Posting包含了關(guān)于一個(gè)詞條在一個(gè)Document中出現(xiàn)的所有位置(用一個(gè)int[]數(shù)組來(lái)描述)、頻率(int)、該詞條對(duì)應(yīng)的所有的Payload信息(用Payload[]來(lái)描述,因?yàn)橐粋€(gè)詞條具有了頻率信息,自然就對(duì)應(yīng)了多個(gè)Payload)。
關(guān)于Payload可以參考文章 Lucene-2.2.0 源代碼閱讀學(xué)習(xí)(23) 。
_N.prx文件文件寫(xiě)入的內(nèi)容都是與位置相關(guān)的數(shù)據(jù)。
從上面可以看出:
_N.frq文件(即freq流)中寫(xiě)入的內(nèi)容是:
(1) 一個(gè)詞條所在的Document的編號(hào);
(2) 每個(gè)詞條在Document中頻率(即:出現(xiàn)的次數(shù));
_N.prx文件(即prox流)中寫(xiě)入的內(nèi)容是:
其實(shí)主要就是Payload的信息,如:一個(gè)詞條對(duì)應(yīng)的Payload的長(zhǎng)度信息、起始偏移量信息;
_N.nrm文件
在DocumentWriter類的addDocument()方法中可以看到調(diào)用了writeNorms()方法:
writeNorms(segment);
也是根據(jù)生成的segment的名稱_ram_N來(lái)創(chuàng)建一個(gè)輸出流,看writeNorms()方法的定義:
private final void writeNorms(String segment) throws IOException {
??? for(int n = 0; n < fieldInfos.size(); n++){
????? FieldInfo fi = fieldInfos.fieldInfo(n);
????? if(fi.isIndexed && !fi.omitNorms){
??????? float norm = fieldBoosts[n] * similarity.lengthNorm(fi.name, fieldLengths[n]);
??????? IndexOutput norms = directory.createOutput(segment + ".f" + n);
??????? try {
????????? norms.writeByte(Similarity.encodeNorm(norm));
??????? } finally {
????????? norms.close();
??????? }
????? }
??? }
}
將一些標(biāo)準(zhǔn)化因子的信息,都寫(xiě)入到了_N.nrm文件。其中每個(gè)segment對(duì)應(yīng)著一個(gè)_N.nrm文件。
關(guān)于標(biāo)準(zhǔn)化因子可以參考文章 Lucene-2.2.0 源代碼閱讀學(xué)習(xí)(19) ,或者直接參考Apache官方網(wǎng)站 http://lucene.apache.org/java/docs/fileformats.html#Normalization%20Factors 。
關(guān)于不同格式的索引文件的內(nèi)容示例
為了直觀,寫(xiě)一個(gè)簡(jiǎn)單的例子:
package org.shirdrn.lucene;
import java.io.IOException;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.LockObtainFailedException;
public class LuceneIndexFormat {
public static void main(String[] args) {
?? String indexPath = "E:\\Lucene\\myindex";
?? String maven = "Maven is a software project management and comprehension tool.";
?? String lucene = "Apache Lucene is a search engine written entirely in Java.";
?? Document doc1 = new Document();
?? doc1.add(new Field("maven",maven,Field.Store.YES,Field.Index.TOKENIZED));
?? Document doc2 = new Document();
?? doc2.add(new Field("lucene",lucene,Field.Store.YES,Field.Index.TOKENIZED));
?? try {
??? IndexWriter indexWriter = new IndexWriter(indexPath,new StandardAnalyzer(),true);
??? indexWriter.setUseCompoundFile(false);
??? indexWriter.addDocument(doc1);
??? indexWriter.addDocument(doc2);
??? indexWriter.close();
?? } catch (CorruptIndexException e) {
??? e.printStackTrace();
?? } catch (LockObtainFailedException e) {
??? e.printStackTrace();
?? } catch (IOException e) {
??? e.printStackTrace();
?? }
}
}
運(yùn)行主函數(shù)后,在指定的索引目錄下生成了索引文件,而且是同一個(gè)索引段,如圖所示:
使用UltraEdit-32打開(kāi)_0.fnm文件,可以看到內(nèi)容如下所示:

就是我們?cè)诔绦蛑性O(shè)置的,即:
?? doc.add(new Field("maven",maven,Field.Store.YES,Field.Index.TOKENIZED));
?? doc.add(new Field("lucene",lucene,Field.Store.YES,Field.Index.TOKENIZED));
就是這兩個(gè)Field的name。
使用UltraEdit-32打開(kāi)_0.fdt文件,可以看到內(nèi)容如下所示:
其實(shí),就是Field的內(nèi)容。(上面的文本內(nèi)容實(shí)際上存儲(chǔ)在一行)
使用UltraEdit-32打開(kāi)_0.fdx文件,可以看到內(nèi)容如下所示:

其實(shí),就是在_0.fdt文件中,兩個(gè)Field的存放位置。
第一個(gè)Field是從0位置開(kāi)始的,第二個(gè)是從42(這里是16進(jìn)制,十進(jìn)制為66)位置開(kāi)始的。
使用UltraEdit-32打開(kāi)_0.nrm文件,可以看到內(nèi)容如下所示:

這里是標(biāo)準(zhǔn)化因子信息。
(關(guān)于標(biāo)準(zhǔn)化因子可以參考文章 Lucene-2.2.0 源代碼閱讀學(xué)習(xí)(19) ,或者直接參考Apache官方網(wǎng)站 http://lucene.apache.org/java/docs/fileformats.html#Normalization%20Factors 。)
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
