自己动手写分布式搜索引擎
上QQ阅读APP看书,第一时间看更新

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是为了得到整数结果,然后参与后续的位移运算。