分享

.索引的建立和优化

 CevenCheng 2012-05-12
 

6.索引的建立和优化

分类: 搜索引擎 211人阅读 评论(0) 收藏 举报

索引的建立和优化

1.        索引建立的过程

1.1 Lucene 索引机制

首先,将不同格式的文件通过相应的解析器解析成文本形式

然后,调用分析器对文本进行分析,主要是做分词,以构建倒排索引

最后,建立索引。也就是先将逻辑 Document 加入到 IndexWriter 中,然后利用 IndexWriter 和本地文件系统的关联,将索引建立在本地硬盘上。

1.2 文本分析

 1 )对不同文本使用不同的分析器

IndexWriter writer = new IndexWriter(indexPath, new StandardAnalyzer());

writer.addDocument(doc);

这种情况下,会按照相同的分析器分析不同类型的文档(如不同语言),会导致一种文档分析得好,另一种文档分析得差。 Lucene 提供一个解决方案,就是实现 IndexWriter  addDocument 方法的重载。

writer.addDocument(doc, 分析器实例 );

示例 1  FileIndexer.java

import org.apache.lucene.document.Document;

import org.apache.lucene.document.Field;

import org.apache.lucene.index.IndexWriter;

 

import jeasy.analysis.MMAnalyzer;

import org.apache.lucene.analysis.standard.StandardAnalyzer;

 

import java.io.*;

 

import tool.FileText;

 

public class FileIndexer

{

       public static void main(String[] args) throws java.io.IOException

       {

              String indexPath = "file";

             

              //IndexWriter

              IndexWriter writer = new IndexWriter(indexPath,new StandardAnalyzer());     

                    

                     //Document          

                     Document doc = new Document();

                     File f = new File("doc/ 黑帝 .htm");

                    

                     //Field -name

                     String name = f.getName();

                     Field field = new Field("name",name ,Field.Store.YES, Field.Index.TOKENIZED);

                     //add field

                     doc.add(field);

                           

                     //Field -content

                     String content = FileText.getText(f);

                     field = new Field("content", content ,Field.Store.YES, Field.Index.TOKENIZED);

                     //add field

                     doc.add(field);

 

                     //Field -path

                     String path = f.getPath();

                     field = new Field("path", path ,Field.Store.YES, Field.Index.NO);

                     //add field

                     doc.add(field);

                                  

                     //add document

                     writer.addDocument(doc,new MMAnalyzer()); // 使用重载的 addDocument 方法

                    

                     /**************************************************************/

                     doc = new Document();

                     f = new File("doc/China.htm");

                    

                     //Field -name

                     name = f.getName();

                     field = new Field("name",name ,Field.Store.YES, Field.Index.TOKENIZED);

                     //add field

                     doc.add(field);

                           

                     //Field -content

                     content = FileText.getText(f);

                     field = new Field("content", content ,Field.Store.YES, Field.Index.TOKENIZED);

                     //add field

                     doc.add(field);

 

                     //Field -path

                     path = f.getPath();

                     field = new Field("path", path ,Field.Store.YES, Field.Index.NO);

                     //add field

                     doc.add(field);

                                  

                     //add document

                     writer.addDocument(doc,new StandardAnalyzer()); // 使用重载的 addDocument 方法

             

              //close IndexWriter

              writer.close();

             

              //message

              System.out.println("File Index Created!");

       }    

}

 

 2 )说说分析器

分析器和解析器不是一个事物,解析器用来解析文件,从中提取出所需文本,如标题、正文、时间等;而分析器用来分析文本内容,或者说分析文字,它在解析器之后使用,面向的是解析器提取出的文本。

为了过滤某些搜索关键词,我们既可以在前端过滤请求,也可以在分析器中进行。

Lucene 没有自己开发语言分析程序,它把这个工作交给 JavaCC 来做。

Lucene 内置了一些分析器,其中四个最简单的分析器如下:

l         WhitespaceAnalyzer :在空格处进行词语切分,适用于西文。

l         SimpleAnalyzer :在非字母字符处切分文本,并将其转换成小写形式;空格也属于非字母字符哦。

l         StopAnalyzer :在非字母字符处切分文本,然后小写化,再移除忽略词。

l         StandardAnalyzer 基于某种语法规则将文本切分为词语块,这种语法规则可以识别 Email 地址、首字母缩写词、汉语 - 日语 - 汉语字符、字母数字等。这里的关键是语法规则。 Lucene 用自己的语法规则开发了 StandardAnalyzer ,还用单字切分法开发了 ChineseAnalyer ,有人用二分法规则开发了CJKAnalyzer ,还有人用词典法开发了 IKAnalyzer 

 3 )开发自己的分析器

Analyzer 是所有分析器的基类,要实现自己的分析器,必须继承 Analyzer 类。只需要覆盖一个方法,

public TokenStream tokenStream(String fieldName, Reader reader)

 

建立的分析器结构往往如下所示:

//GeniusAnalyzer

package org.apache.lucene.analysis;

 

import java.io.Reader;

public class GeniusAnalyer extends Analyer{

       public TokenStream tokenStream(String fieldName, Reader reader){

              return new GeniusTokenizer(reader);

       }

}

这是个最简单的分析器框架,为了增加其功能还可以加入忽略词等内容。这个分析器将文本内容传递给了一个 Tokenizer 类(通过 Reader 参数),实际上分词的算法是在具体的 Tokenizer 类中实现的。

//GeniusTokenier

 

import org.apache.lucene.analysis.Token;

import org.apache.lucene.analysis.Tokenizer;

 

public class GeniusTokenizer extends Tokenizer{

       public GeniusTokenizer(Reader reader){

      

       }

       public Token next(){

             

       }

}

 Tokenizer 类中可以实现词典的载入和文本匹配等功能。

 4 )根据不同 Field 使用不同的分析器

上面讲到了 Lucene 对所有 Document 使用相同分析器的做法,以及对不同的 Document 使用不同分析器的做法。此外, Lucene 还可以对一个 Document 的不同 Field 使用不同的分析器。这是因为,一个文件里面可能混杂着不同语言的文本,如中文论文里面的英文摘要。

这项功能是通过类 PerFieldAnalyzerWrapper ,它直接继承 Analyzer 类,使用addAnalyzer(java.lang.String, org.apache.lucene.analysis.Analyzer) 为不同 Field 添加不同的 Analyzer 

示例:

文本内容

标题 : 天道论

 

作者 : 于天恩

 

Abstract:

love is beautiful!

I love you China!

I love you mama!

I love you papa!

 

时间 :2007-6-1

程序代码, FileIndexer.java

              String indexPath = "file";

             

              //IndexWriter

              IndexWriter writer = new IndexWriter(indexPath,new StandardAnalyzer());     

                /* title           *author         *abs             *time       */

// 创建 PerFieldAnalyzerWrapper 对象 wr ,然后使用 wr 设定不同字段的分析器,PerFieldAnalyzerWrapper 方法中指定了一个 Analyzer ,这是一个默认的分析器,如果某个字段没有用addAnalyzer 方法设置 Analyzer ,就使用这个默认的 Analyzer ,其它地方无特别代码

              //PerFieldAnalyzerWrapper          

              PerFieldAnalyzerWrapper wr = new PerFieldAnalyzerWrapper(new StandardAnalyzer());

            wr.addAnalyzer("title", new MMAnalyzer());

            wr.addAnalyzer("author", new MMAnalyzer());

            wr.addAnalyzer("abs", new StandardAnalyzer());

            wr.addAnalyzer("time", new StandardAnalyzer());

 

 

              //Fields 各个字段

              Field field = new Field("title",title ,Field.Store.YES, Field.Index.TOKENIZED);

              doc.add(field);

                           

              field = new Field("author",author ,Field.Store.YES, Field.Index.UN_TOKENIZED);

              doc.add(field);

 

              field = new Field("abs",abs ,Field.Store.YES, Field.Index.TOKENIZED);

              doc.add(field);

 

              field = new Field("time",time ,Field.Store.YES, Field.Index.NO);

              doc.add(field);

                                  

              //add document

              writer.addDocument(doc,new MMAnalyzer());

 

              //close IndexWriter

              writer.close();

 

2.        索引文件的生成

 

IndexWriter 在构造的时候和物理文件进行了关联,我们在程序中构建一个逻辑上的索引器,最后 Lucene 生成索引文件。

DocumentWriter 类是实际其作用的,它可以获取 Field 信息,建立倒排索引,和文件系统交互。里面涉及排序算法、倒排算法等子问题。

 

索引的格式

Lucene 索引由许多索引块构成。

l         segments 文件是主要的索引块,其中含有主要的索引信息。

l         fnm 文件存储了 Field 的名称,

l         fdt 文件存储了所有设置了保存属性( Store.YES )的 Field 数据,

l         fdx 文件用于存储文档在 fdt 文件中的位置;

l         cfs 文件是复合式索引格式的索引文件,相当于把多个索引文件合并起来,从而检索索引文件的数量。

Lucene 通过这些文件记录 Field 信息,索引项出现频率信息和索引位置信息。

 

3.        索引的优化

3.1 优化的本质

建立索引的目的是为了搜索,搜索实际上是 IO 操作。当索引数量增加、文件增大的时候, I/O 操作就会缓慢,所以需要优化。方法有:

l         利用缓存,减少磁盘读写频率

l         减少索引文件大小和数量

3.2 复合式索引

使用复合式索引可以有效减少索引文件的数量,是索引优化的重要方法。 IndexWriter SetUseCompoundFile 方法,可以设置是否使用复合式索引格式,默认为 True

IndexWriter. SetUseCompoundFile(false)

3.3 调整优化参数

Lucene 提供了 3 个优化参数,可以优化磁盘写入的频率和内存消耗。

(1) mergeFactor

控制索引块的合并频率和大小,默认值 10 

在将 Document 写入磁盘之前, mergeFactor 参数控制内存中存储的 Document 对象的数量以及合并多个索引块的频率。

每当向索引增加 10  Document 的时候,就会有一个索引块被建立起来;当磁盘上有 10 个索引块的时候,将被合并成一个大块。这个大块中含有 100  Document 。然后,继续积累,大块会合并成更大的块,这个更大块有 1000  Document 。因此,任意时刻索引中的块数都不会大于 9 ,并且每个合并后的块的大小都为 10 的乘方。

注意:该参数会受到 maxMergeDocs 参数的制约,导致每个索引块中含有的 Document 数量都不可以大于maxMergeDocs 参数的值。

使用较大的 mergeFactor 会让 Lucene 占用更多内存,同时使磁盘写入数据频率降低,因此加速了索引过程。较小的 mergeFactor 值能减少内存消耗,并使索引更新频率升高,使数据实时性更强,但降低了索引速度。

所以,较大的 mergeFactor 参数适用于批量索引的情况,较小的 mergeFacotr 参数适用于交互性较强的索引。

IndexWriter 类使用 setMergeFactor(int mergeFactor) 方法进行设置

(2) maxMergeDocs

设置每个索引块的文档数量,默认值 Integer.MAX_VALUE

(3) maxBufferedDocs

限制内存中的文档数量,默认值 10 。值越大,越消耗内存,同时磁盘 IO 越少。该参数的意义是用内存空间换取更快的索引。该参数并不影响磁盘上索引块的大小。

 

总结:增大 mergeFactor  maxBufferedDocs 可以提高索引速度。同时, mergeFactor 过大会导致索引块中文件过多,搜索速度减慢。

               IndexWriter writer = new IndexWriter(indexPath,new MMAnalyzer());    

                            

               // 内存中文档最大值 50

               writer.setMaxBufferedDocs(50);

              

               // 内存中存储 50 个文档时写成磁盘一个块

               writer.setMergeFactor(50);

 

3.4 内存缓冲器和索引合并

内容:首先在内存中建立索引,然后将建立的索引集中写到磁盘,这样避免了在磁盘中一次次的增加索引文件,从而加快索引速度。

内存缓冲器的构建,使用 IndexWriter 的另一种构造方法

public IndexWriter(Dirctory d, Analyzer a, Boolean create) throws IOException

Directory 参数: RAMDirectory 在内存中建立索引, FSDirectory 在磁盘中建立索引

在通过修改 mergeFactor  maxMergeDocs  maxBufferedDocs 参数提高性能的前提下,如果希望进一步提高性能,就可以把 RAMDirectory 作为缓冲器,先将索引文件缓存在缓冲器中,再把数据写入基于 FSDirectory的索引中。

RAMDirectory 使用

在内存中创建索引

RAMDirectory rd = new RAMDirectory();

IndexWriter writer = new IndexWriter(rd,new StandardAnalyzer());

 

在内存中执行搜索

RAMDirectory rd = new RAMDirectory();

IndexSearcher searcher = new IndexSearcher(rd);

FSDirectory 使用

在文件系统中创建索引

FSDirectory fd = FSDirectory.getDirectory("index");

IndexWriter writer = new IndexWriter(fd,new StandardAnalyzer());

在文件系统中执行搜索

FSDirectory fd = FSDirectory.getDirectory("index");

IndexSearcher searcher = new IndexSearcher(fd);

通过内存缓冲器将 RAMDirectory 的索引内容写入到 FSDirectory 的方法:

l         建立基于 RAMDirectory 的方法

l         向基于 RAMDirectory 的索引中添加文档

l         建立基于 FSDirectory 的索引

l         把缓存在 RAMDirectory 中的所有数据写入到 FSDirectory

 

在内存中创建索引

RAMDirectory rd = new RAMDirectory();

IndexWriter rw = new IndexWriter(rd,new StandardAnalyzer());

 

关闭 IndexWriter

iw.close();

 

创建文件系统索引

FSDirectory fd = FSDirectory.getDirectory("index");

IndexWriter writer = new IndexWriter(fd,new StandardAnalyzer());

 

将内存索引并入文件系统索引

writer.addIndexes(new Directory[]{rd});

writer.close();

索引合并

IndexWriter  addIndexes 方法,参数是 Directory 类型的数组,可以同时合并多个索引,只需为不同的索引目录建立 Directory 对象即可。

在单线程环境中,可以让每个线程通过 RAMDirectory 建立各自的索引,最后通过 FSDirectory 建立单一的索引文件,效率更高。

如果要把文件系统中的索引读入内存,使用这个方法

RAMDirectory rd = new RAMDirectory(fd);

 

3.5 限制每个 Field 的词条数量

public void setMaxFieldLength(int maxFieldLength)

限定某个 Field 可被拆分出的最大词条数量,通常在 10000 以内。

设置原因:如果某个 Field 被拆分成了大量的词条,将消耗大量的内存,容易导致内存溢出,这个问题在大文档的情况下容易发生。

        IndexWriter writer = new IndexWriter(indexPath,new StandardAnalyzer());     

       

        int num = writer.getMaxFieldLength();

        System.out.println("Before : " + num);  // 10000

              

        writer.setMaxFieldLength(100);  // 设置

 

int num = writer.getMaxFieldLength();

        System.out.println("Before : " + num);  // 值变为 100

 

3.6 索引本身的优化

public void optimize()   throws IOException

该方法专门用来优化索引,它使得多个索引文件合并成单个文件,经过优化的索引比未优化的索引包含的索引文件要少得多。该优化只是提高搜索操作的速度,对索引过程没有影响。

这项优化是通过把已存在的索引块合并成一个全新的索引块来完成的,在优化过程中,新的索引块建立完成钱,旧的索引块不会被删除,故索引占用的磁盘空间会变为原来的两倍。优化完成后,占用磁盘空间会回到优化前的状态。

索引优化的对象可以是多文件索引或复合索引。虽然可以在任意时刻进行优化,但最佳时机是在索引建立完成后。

FSDirectory fd = FSDirectory.getDirectory("index");

        IndexWriter writer = new IndexWriter(fd,new StandardAnalyzer());

        writer.optimize();

        //close IndexWriter

        writer.close();

 

3.7 查看索引的过程

如果把 IndexWriter 的公有变量 infoStream 设定为 PrintStream 的一种,诸如 System.out 等,就可以使Lucene 输出关于它进行索引操作时的一些具体信息,对进行精细的索引优化很有帮助。

RAMDirectory rd = new RAMDirectory();

IndexWriter writer = new IndexWriter(rd,new StandardAnalyzer());

输出到屏幕   

writer.setInfoStream(System.out);

输出到文本

PrintStream ps = new PrintStream("log.txt");       

writer.setInfoStream(ps);

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多