Lucene索引的建立和优化
索引的建立
对不同的文本使用不同的分析器
普通情况下,建立索引器,并向索引器添加文档的语句如下:
IndexWriter writer=new IndexWriter(indexPath,new StandardAnalyzer());
Writer.addDocument(doc);
这种情况下,所有的文档都会被同一种分析器所处理。我们要处理的文档中即包含中文,也包含法文,或者其他的语言的时候,如果只按一种分析器去处理,那么就会导致一种文档分析得好,另一种文档分析得差。
为了解决这个问题,就可以使用下面的语句:
Writer.addDocument(doc,分析器实例);
通过这个方法,可以对不同的文本使用不同的分析器。
Lucene内置的分析器
Lucene使用JavaCC来生成分析器。
Lucene内置了一些分析器。
分析器类型
分析器原理简述
WhitespaceAnalyzer
在空格处进行词语的切分
SimpleAnalyzer
在非字母字符处切分文本,并转化成小写形式
StopAnalyzer
在非字母字符处切分文本,然后小写化,再移除忽略词
StandardAnalyzer
基于某种语法规则将文本切分成词语快
Lucene还内置了很多其他类型的分析器。还有很多有志之士贡献了自己开发的分析器。
开发自己的分析器
这里只说开发的具体思路,不谈分析器的具体思路,我也不太会。
1, 继承Analyzer,覆盖tokenStream的方法
package org.apache.lucene.analysis;
import Java.io.Reader;
public class MyAnalyzer extends Analyzer
{
public TokenStream tokenStream(String fieldName, Reader reader)
{
return new MyTokenizer(reader);
}
}
2, 实现自己的Tokenizer
import org.apache.lucene.analysis.Token;
import org.apache.lucene.analysis.Tokenizer;
public class MyTokenizer extends Tokenizer
{
public MyTokenizer(Reader reader)
{
}
public Token next()
{
}
}
根据不同的Field使用不同的分析器
如果一篇中文的论文,里面有一段英文的摘要,我们要对摘要信息进行索引,应该怎么办呢?Lucene中的PerFieldAnalyzerWrapper可以解决这个问题。
这个类继承自Analyzer,使用addAnalyzer(字段名称,分析器实例);
例如:
PerFieldAnalyzerWrapper wr = new PerFieldAnalyzerWrapper(new StandardAnalyzer());
wr.addAnalyzer("title", new MMAnalyzer());
wr.addAnalyzer("author", new MMAnalyzer());
wr.addAnalyzer("abstract", new StandardAnalyzer());
wr.addAnalyzer("time", new StandardAnalyzer());
索引文件的生成
生成的过程
IndexWriter调用DocumentWriter,DocumentWriter能获得Field的信息,建立倒排索引,与文件系统交互。
索引文件的格式
文件类型 存储含义
Segments 索引块
Fnm Field的名称
Fdt 存储了所有设有Store.YES的Field的数据
Fdx 存储文档在fdt中的位置
Cfs 复合式索引格式的索引文件
索引的优化
优化的原则
1, 利用缓存,建设磁盘的读写频率。
2, 简述索引文件的大小和数量。
复合式索引格式
IndexWriter具有setUseCompoundFile方法,通过这个方法可以设置是否使用复合式索引。默认值是True。
使用复合式索引可以减少索引文件的数量。
调整索引优化参数
第一个优化的参数:mergeFactor。
用来控制索引块的合并频率和大小,默认值是10。这个参数控制在内存中存储的Document对象的数量以及合并多个索引块的频率。在将它们作为单个块写入磁盘之前,Lucene在内存中默认存储10个Document对象。当mergeFactor的值为10也意味着磁盘上的块数达到10的乘方的时候,Lucene会将这些块合并为一个段。
也就是说,每当向索引增加10个Document的时候,就会有一个索引块被建立起来。当磁盘上有10个索引块的时候,将被合并成一个大块。这个大块含有100个Document.然后,继续积累,当到10个大块的时候,将被合并成一个更大的索引块。这个索引块中有1000个索引。
但是这个参数,受到maxMergeDocs的制约。由此导致每个索引块中含有的Document数量都不可以大于maxMergeDocs的值。
使用较大参数的mergeFactor会让Lucene使用更多的内存,同时使得磁盘写入数据的频率降低,因此加速了索引的过程。但是,大的mergeFactor意味着低频率的合并,回导致索引中的索引文件数增多。这样会降低搜索速度。
使用较小参数的mergeFactor会减少内存的消耗,并使索引更新的频率升高。这样做使得数据的实时性更强,但是也降低了索引过程的速度。
IndexWriter类的setMergeFactor(int mergeFactor)方法来设置mergeFactor的大小。
第二个优化的参数:maxMergeDocs。
用来限制每个索引块的文档数量,默认值是Integer.MAX_VALUE;
较大的maxMergeDocs可以加快索引的速度,但是更耗内存。
较大的maxMergeDocs参数适用于批量索引的情况,较小的maxMergeDocs参数适用于交互性较强的索引。
IndexWriter.setMaxMergeDocs(int maxMergeDocs)方法来设置maxMergeDocs的大小。
第三个优化的参数:maxBuffer Docs。
这个参数用于控制内存中文档的数量。默认值是10.
这个值越大,在内存中存储文档就越多,越消耗内存,同时磁盘的I/O越少。
这些参数都是针对Lucene2.1而言的,好像Lucene2.4有了变化。
内存缓冲器与索引合并
为了将索引放在内存中缓冲起来,我们需要内存缓冲器。
Public IndexWriter(Directory d,Analyzer a,Boolean create) throws IOException
Directory分为两种,一种是RAMDirectory或者FSDirectory得实例。
使用RAMDirectoy,就是在内存中建立索引。这种索引建立的方式特别快,但是不能永久保存。
使用FSDirectory,就是在磁盘中建立索引。建立起来的索引能永久保存,但是索引速度慢。
如果把RAMDirectory作为缓冲器,先将索引文件缓存在缓冲器中,再把数据写入基于FSDirectory的索引中,从而达到改善性能的目的。
RAMDirectory的使用方法
//创建缓冲器对象
RAMDirectory rd = new RAMDirectory();
//创建索引器
IndexWriter writer = new IndexWriter(rd,new StandardAnalyzer());
//创建搜索器
IndexSearcher searcher = new IndexSearcher(rd);
FSDirectory的使用方法
FSDirectory fd = FSDirectory.getDirectory("index");
IndexWriter writer = new IndexWriter(fd,new StandardAnalyzer());
IndexSearcher searcher = new IndexSearcher(fd);
RAMDirectory和FSDirectory相结合使用
//创建基于RAMDirectory的索引
RAMDirectory rd = new RAMDirectory();
IndexWriter iw = new IndexWriter(rd,new StandardAnalyzer());
….
//向基于RAMDirectory的索引中添加文档
iw.addDocument(doc);
iw.close();
//建立基于FSDirectory的索引
FSDirectory fd = FSDirectory.getDirectory("mix");
IndexWriter writer = new IndexWriter(fd,new StandardAnalyzer());
//把缓存在RAMDirectory中的所有数据写入FSDirectory
writer.addIndexes(new Directory[] {rd});
writer.close();
同样可以反过来用,将文件系统中的索引读入内存中,就需要使用如下的方法:
RAMDirectory rd=new RAMDirectory();
限制每个Field的词条数量
对于Document德某个Field,我们可以限定它可被拆分的最大的词条数量。
使用IndexWriter如下方法即可:
Public void setMaxFieldLength(int maxFieldLength);
如果某个Field别拆分了大量的词条,那么将消耗大量的内存。很容易导致内存溢出,这个问题在大文档的情况下尤其容易发生,所以要限定。
通常,maxFieldLength不应超过10000。
索引本身的优化
IndexWriter
Public void optimize() throws IOException
这个方法可以提高索引搜索操作的速度,但是不会影响建立的速度。
查看索引的过程
IndexWriter的setInfoStream可以用来查看索引的过程。
例如:
PrintStream ps = new PrintStream("log.txt");
writer.setInfoStream(ps);
(本文转自科学网)