论坛首页 入门技术论坛

Lucene4.3进阶开发之潇湘夜雨(十七)

浏览 3726 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2014-02-13  
Lucene的索引过程,非常简洁,我们只需要调用Lucene提供的几个API方法即可,看似简单的过程,其实在lucene的内部,是非常复杂巧妙的,只不过它给我们提供的外部API非常精简轻便。
<pre name="code" class="java">Directory directory=FSDirectory.open(new File("E:\\1111111111111111111\\1\\"));//打开存放索引的路径
IndexWriter writer1=new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_45, new IKAnalyzer(true)));
        Document doc=new Document();
        doc.add(new StringField("id", "11", Store.YES));
        doc.add(new StringField("name","三劫散仙", Store.YES));
        writer1.addDocument(doc);

writer1.forceMerge(1);//优压缩段
writer1.commit();//提交数据 </pre>

如上所示,就是一个索引的基本流程。当然上面的这个流程显的太简单了。而真正的情况可能要比这个复杂的多。

Lucene的索引过程主要分为3个主要的步骤:
1:将原始文档转换为文本
2:使用分词器分析文本
3:将分析好的文本保存到索引里。



当然,如果我们的应用是基于垂直搜索的,可能我们的数据源就是我们的数据库,或者是一些例如excel,word,pdf,txt之类的文件,如果是文件的话我们可以使用Apache Tika来解析文本,然后在分词后存入索引,但是Lucene是不直接具备这样的功能的,需要我们提前自己把数据的文本内容抽取出来。

当我们把我们所需要的文本抽取出来之后,下一步就开始分析我们的索引内容了,当然这个过程是非常复杂的,不过只需要提前设置好分词器就可以方便的完整这个过程,为什么要分词呢?
我们都知道lucene是基于倒排索引的形式,来构建整个索引的,把原来的根据一篇文章查找关键词,变成了根据关键词查找文章,这样一个反转,类似,书本的页码一样,就能够非常高效的检索了,在分析过程中,lucene会对一些token,做净化,清洗,归一,以及去掉一些禁用词等等,这个过程都是由Analyzer来完成的。当处理好最终的token时,我们就可以使用IndexWriter对象来把数据添加到磁盘的索引里,为以后的检索做准备。

下面我们来详细介绍下分词的过程,一般情况下分词指的是将Lucene中的Field文本,转换为最基本的索引表示单元,也就是term,分词器对分析操作进行了一系列的封装通过Tokenizer和一些TokenFilter,执行若干操作,将文本转换成便于索引查找的语汇单元。
这些操作可能包括:提取文本关键词,去除标点符号,去掉音调符号(拼音和英文),转换大小写,去除常用词,将单词还原成词干,或者将单词转为基本形式,词形归并,或者我们还需要在分词的过程中注入同义词等等。



文本中的每个语汇单元,都携带了一个文本值和其他的一些元数据信息,除了自身的文本值(即文本本身),还有原始文本从起点到终点的偏移量,语汇单元的类型,位置增量,标志位,和载荷。
注意起点的偏移量指的是语汇单元文本的的起始字符在原始文本中的位置,终点的偏移量指的是语汇单元终止字符的下一个位置,这一点需要注意并不是语汇单元的最后一个字符的位置,是其的下一位,偏移量在一些距离和跨度查询时非常有用,最重要的是这个偏移量对于搜索结果中的高亮显示非常有用,没有这些数据想完成高亮可能就有点麻烦了,但不是说不能完成,我们也可以在前台进行高亮,关于如何在前台高亮,可以参考散仙
修真篇的文章
在这里就不多涉及了。

当所有的文本被语汇成单元后,lucene将会通过一种链式方式,在后台层层把这些信息添加到lucene的各个索引文件里。默认的索引链在DocumentsWriterPerThread中有初始化

<pre name="code" class="java">[static final IndexingChain defaultIndexingChain = new IndexingChain() {

    @Override
    DocConsumer getChain(DocumentsWriterPerThread documentsWriterPerThread) {
      /*
      This is the current indexing chain:

      DocConsumer / DocConsumerPerThread
        --> code: DocFieldProcessor
          --> DocFieldConsumer / DocFieldConsumerPerField
            --> code: DocFieldConsumers / DocFieldConsumersPerField
              --> code: DocInverter / DocInverterPerField
                --> InvertedDocConsumer / InvertedDocConsumerPerField
                  --> code: TermsHash / TermsHashPerField
                    --> TermsHashConsumer / TermsHashConsumerPerField
                      --> code: FreqProxTermsWriter / FreqProxTermsWriterPerField
                      --> code: TermVectorsTermsWriter / TermVectorsTermsWriterPerField
                --> InvertedDocEndConsumer / InvertedDocConsumerPerField
                  --> code: NormsConsumer / NormsConsumerPerField
          --> StoredFieldsConsumer
            --> TwoStoredFieldConsumers
              -> code: StoredFieldsProcessor
              -> code: DocValuesProcessor
    */

    // Build up indexing chain:

      final TermsHashConsumer termVectorsWriter = new TermVectorsConsumer(documentsWriterPerThread);
      final TermsHashConsumer freqProxWriter = new FreqProxTermsWriter();

      final InvertedDocConsumer termsHash = new TermsHash(documentsWriterPerThread, freqProxWriter, true,
                                                          new TermsHash(documentsWriterPerThread, termVectorsWriter, false, null));
      final NormsConsumer normsWriter = new NormsConsumer();
      final DocInverter docInverter = new DocInverter(documentsWriterPerThread.docState, termsHash, normsWriter);
      final StoredFieldsConsumer storedFields = new TwoStoredFieldsConsumers(
                                                      new StoredFieldsProcessor(documentsWriterPerThread),
                                                      new DocValuesProcessor(documentsWriterPerThread.bytesUsed));
      return new DocFieldProcessor(documentsWriterPerThread, docInverter, storedFields);
    }
  };</pre>
经过一系列链式传递后,索引并不会马上写入磁盘中,而是先写入内存中,然后到一定时机会flush到磁盘上,这实际上也是一种优化策略,从而避免了频繁访问磁盘带来的IO开销,当然我们也可以显式得调用commit方法,将其刷新到磁盘上。


  • 大小: 33.8 KB
  • 大小: 37.1 KB
论坛首页 入门技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics