6.索引的建立和优化

来源:互联网 发布:seo页面优化 编辑:程序博客网 时间:2024/05/20 23:31

索引的建立和优化

1.       索引建立的过程

1.1Lucene索引机制

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

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

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

1.2文本分析

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

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

writer.addDocument(doc);

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

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

示例1FileIndexer.java

importorg.apache.lucene.document.Document;

importorg.apache.lucene.document.Field;

importorg.apache.lucene.index.IndexWriter;

 

importjeasy.analysis.MMAnalyzer;

importorg.apache.lucene.analysis.standard.StandardAnalyzer;

 

importjava.io.*;

 

importtool.FileText;

 

public classFileIndexer

{

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

       {

              String indexPath ="file";

             

              //IndexWriter

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

                    

                     //Document          

                     Document doc = newDocument();

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

                    

                     //Field -name

                     String name = f.getName();

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

                     //add field

                     doc.add(field);

                           

                     //Field -content

                     String content =FileText.getText(f);

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

                     //add field

                     doc.add(field);

 

                     //Field -path

                     String path = f.getPath();

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

                     //add field

                     doc.add(field);

                                  

                     //add document

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

                    

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

                     doc = new Document();

                     f = newFile("doc/China.htm");

                    

                     //Field -name

                     name = f.getName();

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

                     //add field

                     doc.add(field);

                           

                     //Field -content

                     content =FileText.getText(f);

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

                     //add field

                     doc.add(field);

 

                     //Field -path

                     path = f.getPath();

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

                     //add field

                     doc.add(field);

                                  

                     //add document

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

             

              //close IndexWriter

              writer.close();

             

              //message

              System.out.println("FileIndex 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(StringfieldName, Reader reader)

 

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

//GeniusAnalyzer

package org.apache.lucene.analysis;

 

import java.io.Reader;

public class GeniusAnalyer extendsAnalyer{

       publicTokenStream tokenStream(String fieldName, Reader reader){

              returnnew GeniusTokenizer(reader);

       }

}

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

//GeniusTokenier

 

import org.apache.lucene.analysis.Token;

importorg.apache.lucene.analysis.Tokenizer;

 

public class GeniusTokenizer extendsTokenizer{

       publicGeniusTokenizer(Reader reader){

      

       }

       publicToken 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 = newIndexWriter(indexPath,new StandardAnalyzer());     

               /* title           *author         *abs             *time       */

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

              //PerFieldAnalyzerWrapper          

              PerFieldAnalyzerWrapperwr = new PerFieldAnalyzerWrapper(new StandardAnalyzer());

            wr.addAnalyzer("title", newMMAnalyzer());

            wr.addAnalyzer("author", newMMAnalyzer());

            wr.addAnalyzer("abs", newStandardAnalyzer());

            wr.addAnalyzer("time", newStandardAnalyzer());

 

 

              //Fields 各个字段

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

              doc.add(field);

                           

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

              doc.add(field);

 

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

              doc.add(field);

 

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

              doc.add(field);

                                  

              //add document

              writer.addDocument(doc,newMMAnalyzer());

 

              //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 复合式索引

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

IndexWriter. SetUseCompoundFile(false)

3.3 调整优化参数

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

(1) mergeFactor

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

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

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

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

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

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

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

(2) maxMergeDocs

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

(3) maxBufferedDocs

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

 

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

               IndexWriterwriter = 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在磁盘中建立索引

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

RAMDirectory 使用

在内存中创建索引

RAMDirectory rd = new RAMDirectory();

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

 

在内存中执行搜索

RAMDirectory rd = new RAMDirectory();

IndexSearcher searcher = newIndexSearcher(rd);

FSDirectory 使用

在文件系统中创建索引

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

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

在文件系统中执行搜索

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

IndexSearcher searcher = newIndexSearcher(fd);

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

l        建立基于RAMDirectory的方法

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

l        建立基于FSDirectory的索引

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

 

在内存中创建索引

RAMDirectory rd = new RAMDirectory();

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

 

关闭IndexWriter

iw.close();

 

创建文件系统索引

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

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

 

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

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

writer.close();

索引合并

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

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

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

RAMDirectory rd = new RAMDirectory(fd);

 

3.5 限制每个Field的词条数量

public void setMaxFieldLength(intmaxFieldLength)

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

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

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

       

        intnum = 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");

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

        writer.optimize();

        //closeIndexWriter

        writer.close();

 

3.7 查看索引的过程

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

RAMDirectory rd = new RAMDirectory();

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

输出到屏幕  

writer.setInfoStream(System.out);

输出到文本

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

writer.setInfoStream(ps);

原创粉丝点击