3.2.10 定制索引存储结构
开发一个高效的搜索引擎是一项有挑战性的工作。Lucene底层代码读起来往往很费劲。可以自己通过codec来定制编码和索引的结构。
Lucene 4相对更早的版本,一个很大的变化就是提供了可插拔的编码器架构,可以自行定义索引结构,包括词元、倒排列表、存储字段、词向量、已删除的文档、段信息、字段信息。codec在Lucene中的结构如图3-7所示。
图3-7 codec在Lucene中的结构
codec直接传递给SegmentReader来编码索引格式。提供枚举类的实现给SegmentReader。提供索引文件的写入器给IndexWriter。
Lucene 4中已经提供了多个codec的实现,其中Lucene40是默认编码器Lucene40Codec。为了兼容更早的版本,提供了只读的Lucene3xCodec,可以用来读取Lucene 3.x创建的索引,但不能使用该编码器创建Lucene3.x的索引。
PerFieldCodec用来支持不同的列使用不同的读写格式。
lucene-codecs-6.3.0.jar中包含一些额外的codec,和其他的codec写入到压缩的二进制文件不一样,SimpleTextCodec把所有的投递列表写到可读的文本文件。SimpleTextCodec适合用来学习,不建议在生产环境中使用。
Analyzer analyzer = new StandardAnalyzer(); IndexWriterConfig iwc = new IndexWriterConfig(analyzer); iwc.setCodec(new SimpleTextCodec()); iwc.setUseCompoundFile(false); Directory directory = FSDirectory.open(new File("F:/lucene/index")); IndexWriter writer = new IndexWriter(directory, iwc); // index a few documents writer.addDocument(createDocument("1", "青菜鸡肉")); writer.addDocument(createDocument("2", "老鸭粉丝汤")); writer.addDocument(createDocument("3", "辣子鸡丁")); writer.close();
主要产生5个文件:_0.len、_0.fld、_0.inf、_0.pst和_0.si。其中,pst文件保存倒排索引;fld文件保存存储到索引的原值;inf文件保存文件是如何索引的。
倒排索引在_0.pst文件中。先存某一列的倒排索引,然后再存另外一列的倒排索引。例如,像如下方式写入contents列和id列的倒排索引:
field contents term丁 doc 2 freq 1 pos 3 term丝 doc 1 freq 1 pos 3 term子 doc 2 freq 1 pos 1 term汤 doc 1 freq 1 pos 4 term粉 doc 1 freq 1 pos 2 term老 doc 1 freq 1 pos 0 term肉 doc 0 freq 1 pos 3 term菜 doc 0 freq 1 pos 1 term辣 doc 2 freq 1 pos 0 term青 doc 0 freq 1 pos 0 term鸡 doc 0 freq 1 pos 2 doc 2 freq 1 pos 2 term鸭 doc 1 freq 1 pos 1 field id term 1 doc 0 term 2 doc 1 term 3 doc 2 END
_0.fld文件的内容如下:
doc 0 numfields 2 field 0 name id type string value 1 field 1 name contents type string value青菜鸡肉 doc 1 numfields 2 field 0 name id type string value 2 field 1 name contents type string value老鸭粉丝汤 doc 2 numfields 2 field 0 name id type string value 3 field 1 name contents type string value辣子鸡丁 END
_0.inf文件的内容如下:
number of fields 2 name id number 0 indexed true index options DOCS_ONLY term vectors false payloads false norms false norms type false doc values false attributes 0 name contents number 1 indexed true index options DOCS_AND_FREQS_AND_POSITIONS term vectors false payloads false norms true norms type NUMERIC doc values false attributes 0
codec事实上就是由多组format构成的,一个codec共包含8个format,即PostingsFormat、DocValuesFormat、StoredFieldsFormat、TermVectorsFormat、FieldInfo Format、SegmentInfoFormat、NormsFormat和LiveDocsFormat。例如,StoredFieldsFormat用来处理存储数据的列;TermVectorsFormat用来处理词向量。在Lucene4中可以自行定制各个format的实现。
其他的codec可以转换成这样的标准输出。SimpleTextCodec这样的文件格式没有索引,所以无法快速查找某个词,但是可以用于调试和学习。
在IndexWriterConfig中有setCodec()方法可以设置编解码器,可以用这个IndexWriterConfig创建一个IndexWriter。但在IndexReader类中没有这样的方法。写索引的时候需指定要使用的codec,并且把所使用的codec的名字写入索引的每个段中。
在读索引的时候(当打开一个IndexReader的时候),不能再改变编解码器,只能保证索引使用的所有codec都在CLASSPATH中。IndexReader将检查每个段,以确定它是用哪种codec写的,在CLASSPATH中找到这个codec,并用它来打开该段。
IndexWriterConfig iwc = new IndexWriterConfig(analyzer); System.out.println(iwc.getCodec().availableCodecs()); String name = "Lucene42"; iwc.setCodec(iwc.getCodec().forName(name)); IndexWriter writer = new IndexWriter(directory, iwc);
可以在索引中保存和每个词相关的字节数组信息,叫作Payload。首先在分析文本期间生成Payload信息。可以使用PayloadAttribute达到这一点,只需要在分析过程中将该属性添加到Token属性中。使用PayloadHelper将数字编码为Payload,然后就可以设置到PayloadAttribute。例如,编码浮点数:
Payload p = new Payload(PayloadHelper.encodeFloat(42));
注意,这里的PayloadHelper类不在核心包中,而在contrib/common/lucene-analyzers-3.x中。PayloadHelper中的decodeInt()方法从字节数组中得到一个整数。
public static final int decodeInt(byte [] bytes, int offset){ return ((bytes[offset] & 0xFF) << 24) | ((bytes[offset + 1] & 0xFF) << 16) | ((bytes[offset + 2] & 0xFF) << 8) | (bytes[offset + 3] & 0xFF); }
这里的bytes[offset] & 0xFF是为了得到整数结果,然后参与后续的位移运算。