if(textFiles[i].isFile()>>textFiles[i].getName().endsWith(".txt")){
System.out.println("File"+textFiles[i].getCanonicalPath()
+"isbeingindexed");
ReadertextReader=newFileReader(textFiles[i]);
Documentdocument=newDocument();
document.add(Field.Text("content",textReader));
document.add(Field.Text("path",textFiles[i].getPath()));
indexWriter.addDocument(document);
}
}
indexWriter.optimize();
indexWriter.close();
longendTime=newDate().getTime();
System.out.println("Ittook"+(endTime-startTime)
+"millisecondstocreateanindexforthefilesinthedirectory"
+fileDir.getPath());
}
}
正如清单1所示,你可以利用Lucene非常方便的为文档创建索引。
接下来我们分析一下清单1中的比较关键的代码,我们先从下面的一条语句开始看起。
AnalyzerluceneAnalyzer=newStandardAnalyzer();
这条语句创建了类StandardAnalyzer的一个实例,这个类是用来从文本中提取出索引项的。
它只是抽象类Analyzer的其中一个实现。
Analyzer也有一些其它的子类,比如SimpleAnalyzer等。
我们接着看另外一条语句:
IndexWriterindexWriter=newIndexWriter(indexDir,luceneAnalyzer,true);
这条语句创建了类IndexWriter的一个实例,该类也是Lucene索引机制里面的一个关键类。
这个类能创建一个新的索引或者打开一个已存在的索引并为该所引添加文档。
我们注意到该类的构造函数接受三个参数,第一个参数指定了存储索引文件的路径。
第二个参数指定了在索引过程中使用什么样的分词器。
最后一个参数是个布尔变量,如果值为真,那么就表示要创建一个新的索引,如果值为假,就表示打开一个已经存在的索引。
接下来的代码演示了如何添加一个文档到索引文件中。
Documentdocument=newDocument();
document.add(Field.Text("content",textReader));
document.add(Field.Text("path",textFiles[i].getPath()));
indexWriter.addDocument(document);
首先第一行创建了类Document的一个实例,它由一个或者多个的域(Field)组成。
你可以把这个类想象成代表了一个实际的文档,比如一个HTML页面,一个PDF文档,或者一个文本文件。
而类Document中的域一般就是实际文档的一些属性。
比如对于一个HTML页面,它的域可能包括标题,内容,URL等。
我们可以用不同类型的Field来控制文档的哪些内容应该索引,哪些内容应该存储。
如果想获取更多的关于Lucene的域的信息,可以参考Lucene的帮助文档。
代码的第二行和第三行为文档添加了两个域,每个域包含两个属性,分别是域的名字和域的内容。
在我们的例子中两个域的名字分别是"content"和"path"。
分别存储了我们需要索引的文本文件的内容和路径。
最后一行把准备好的文档添加到了索引当中。
当我们把文档添加到索引中后,不要忘记关闭索引,这样才保证Lucene把添加的文档写回到硬盘上。
下面的一句代码演示了如何关闭索引。
indexWriter.close();
利用清单1中的代码,你就可以成功的将文本文档添加到索引中去。
接下来我们看看对索引进行的另外一种重要的操作,从索引中删除文档。
回页首
从索引中删除文档
类IndexReader负责从一个已经存在的索引中删除文档,如清单2所示。
清单2:
从索引中删除文档
FileindexDir=newFile("C:
\\luceneIndex");
IndexReaderir=IndexReader.open(indexDir);
ir.delete
(1);
ir.delete(newTerm("path","C:
\\file_to_index\lucene.txt"));
ir.close();
在清单2中,第二行用静态方法IndexReader.open(indexDir)初始化了类IndexReader的一个实例,这个方法的参数指定了索引的存储路径。
类IndexReader提供了两种方法去删除一个文档,如程序中的第三行和第四行所示。
第三行利用文档的编号来删除文档。
每个文档都有一个系统自动生成的编号。
第四行删除了路径为"C:
\\file_to_index\lucene.txt"的文档。
你可以通过指定文件路径来方便的删除一个文档。
值得注意的是虽然利用上述代码删除文档使得该文档不能被检索到,但是并没有物理上删除该文档。
Lucene只是通过一个后缀名为.delete的文件来标记哪些文档已经被删除。
既然没有物理上删除,我们可以方便的把这些标记为删除的文档恢复过来,如清单3所示,首先打开一个索引,然后调用方法ir.undeleteAll()来完成恢复工作。
清单3:
恢复已删除文档
FileindexDir=newFile("C:
\\luceneIndex");
IndexReaderir=IndexReader.open(indexDir);
ir.undeleteAll();
ir.close();
你现在也许想知道如何物理上删除索引中的文档,方法也非常简单。
清单4演示了这个过程。
清单4:
如何物理上删除文档
FileindexDir=newFile("C:
\\luceneIndex");
AnalyzerluceneAnalyzer=newStandardAnalyzer();
IndexWriterindexWriter=newIndexWriter(indexDir,luceneAnalyzer,false);
indexWriter.optimize();
indexWriter.close();
在清单4中,第三行创建了类IndexWriter的一个实例,并且打开了一个已经存在的索引。
第4行对索引进行清理,清理过程中将把所有标记为删除的文档物理删除。
Lucene没有直接提供方法对文档进行更新,如果你需要更新一个文档,那么你首先需要把这个文档从索引中删除,然后把新版本的文档加入到索引中去。
回页首
提高索引性能
利用Lucene,在创建索引的工程中你可以充分利用机器的硬件资源来提高索引的效率。
当你需要索引大量的文件时,你会注意到索引过程的瓶颈是在往磁盘上写索引文件的过程中。
为了解决这个问题,Lucene在内存中持有一块缓冲区。
但我们如何控制Lucene的缓冲区呢?
幸运的是,Lucene的类IndexWriter提供了三个参数用来调整缓冲区的大小以及往磁盘上写索引文件的频率。
1.合并因子(mergeFactor)
这个参数决定了在Lucene的一个索引块中可以存放多少文档以及把磁盘上的索引块合并成一个大的索引块的频率。
比如,如果合并因子的值是10,那么当内存中的文档数达到10的时候所有的文档都必须写到磁盘上的一个新的索引块中。
并且,如果磁盘上的索引块的隔数达到10的话,这10个索引块会被合并成一个新的索引块。
这个参数的默认值是10,如果需要索引的文档数非常多的话这个值将是非常不合适的。
对批处理的索引来讲,为这个参数赋一个比较大的值会得到比较好的索引效果。
2.最小合并文档数
这个参数也会影响索引的性能。
它决定了内存中的文档数至少达到多少才能将它们写回磁盘。
这个参数的默认值是10,如果你有足够的内存,那么将这个值尽量设的比较大一些将会显著的提高索引性能。
3.最大合并文档数
这个参数决定了一个索引块中的最大的文档数。
它的默认值是Integer.MAX_VALUE,将这个参数设置为比较大的值可以提高索引效率和检索速度,由于该参数的默认值是整型的最大值,所以我们一般不需要改动这个参数。
清单5列出了这个三个参数用法,清单5和清单1非常相似,除了清单5中会设置刚才提到的三个参数。
清单5:
提高索引性能
/**
*Thisclassdemonstrateshowtoimprovetheindexingperformance
*byadjustingtheparametersprovidedbyIndexWriter.
*/
publicclassAdvancedTextFileIndexer{
publicstaticvoidmain(String[]args)throwsException{
//fileDiristhedirectorythatcontainsthetextfilestobeindexed
FilefileDir=newFile("C:
\\files_to_index");
//indexDiristhedirectorythathostsLucene'sindexfiles
FileindexDir=newFile("C:
\\luceneIndex");
AnalyzerluceneAnalyzer=newStandardAnalyzer();
File[]textFiles=fileDir.listFiles();
longstartTime=newDate().getTime();
intmergeFactor=10;
intminMergeDocs=10;
intmaxMergeDocs=Integer.MAX_VALUE;
IndexWriterindexWriter=newIndexWriter(indexDir,luceneAnalyzer,true);
indexWriter.mergeFactor=mergeFactor;
indexWriter.minMergeDocs=minMergeDocs;
indexWriter.maxMergeDocs=maxMergeDocs;
//Adddocumentstotheindex
for(inti=0;iif(textFiles[i].isFile()>>textFiles[i].getName().endsWith(".txt")){
ReadertextReader=newFileReader(textFiles[i]);
Documentdocument=newDocument();
document.add(Field.Text("content",textReader));
document.add(Field.Keyword("path",textFiles[i].getPath()));
indexWriter.addDocument(document);
}
}
indexWriter.optimize();
indexWriter.close();
longendTime=newDate().getTime();
System.out.println("MergeFactor:
"+indexWriter.mergeFactor);
System.out.println("MinMergeDocs:
"+indexWriter.minMergeDocs);
System.out.println("MaxMergeDocs:
"+indexWriter.maxMergeDocs);
System.out.println("Documentnumber:
"+textFiles.length);
System.out.println("Timeconsumed:
"+(endTime-startTime)+"milliseconds");
}
}
通过这个例子,我们注意到在调整缓冲区的大小以及写磁盘的频率上面Lucene给我们提供了非常大的灵活性。
现在我们来看一下代码中的关键语句。
如下的代码首先创建了类IndexWriter的一个实例,然后对它的三个参数进行赋值。
intmergeFactor=10;
intminMergeDocs=10;
intmaxMergeDocs=Integer.MAX_VALUE;
IndexWriterindexWriter=newIndexWriter(indexDir,luceneAnalyzer,true);
indexWriter.mergeFactor=mergeFactor;
indexWriter.minMergeDocs=minMergeDocs;
indexWriter.maxMergeDocs=maxMergeDocs;
下面我们来看一下这三个参数取不同的值对索引时间的影响,注意参数值的不同和索引之间的关系。
我们为这个实验准备了10000个测试文档。
表1显示了测试结果。
表1:
测试结果
通过表1,你可以清楚地看到三个参数对索引时间的影响。
在实践中,你会经常的改变合并因子和最小合并文档数的值来提高索引性能。
只要你有足够大的内存,你可以为合并因子和最小合并文档数这两个参数赋尽量大的值以提高索引效率,另外我们一般无需更改最大合并文档数这个参数的值,因为系统已经默认将它设置成了最大。
回页首
Lucene索引文件结构分析
在分析Lucene的索引文件结构之前,我们先要理解反向索引(Invertedindex)这个概念,反向索引是一种以索引项为中心来组织文档的方式,每个索引项指向一个文档序列,这个序列中的文档都包含该索引项。
相反,在正向索引中,文档占据了中心的位置,每个文档指向了一个它所包含的索引项的序列。
你可以利用反向索引轻松的找到那些文档包含了特定的索引项。
Lucene正是使用了反向索引作为其基本的索引结构。
回页首
索引文件的逻辑视图
在Lucene中有索引块的概念,每个索引块包含了一定数目的文档。
我们能够对单独的索引块进行检索。
图2显示了Lucene索引结构的逻辑视图。
索引块的个数由索引的文档的总数以及每个索引块所能包含的最大文档数来决定。
图2:
索引文件的逻辑视图
回页首
Lucene中的关键索引文件
下面的部分将会分析Lucene中的主要的索引文件,可能分析有些索引文件的时候没有包含文件的所有的字段,但不会影响到对索引文件的理解。
1.索引块文件
这个文件包含了索引中的索引块信息,这个文件包含了每个索引块的名字以及大小等信息。
表2显示了这个文件的结构信息。
表2:
索引块文件结构
2.域信息文件
我们知道,索引中的文档由一个或者多个域组成,这个文件包含了每个索引块中的域的信息。
表3显示了这个文件的结构。
表3:
域信息文件结构
3.索引项信息文件
这是索引文件里面最核心的一个文件,它存储了所有的索引项的值以及相关信息,并且以索引项来排序。
表4显示了这个文件的结构。
表4:
索引项信息文件结构
4.频率文件
这个文件包含了包含索引项的文档的列表,以及索引项在每个文档中出现的频率信息。
如果Lucene在索引项信息文件中发现有索引项和搜索词相匹配。
那么Lucene就会在频率文件中找有哪些文件包含了该索引项。
表5显示了这个文件的一个大致的结构,并没有包含这个文件的所有字段。
表5:
频率文件的结构
5.位置文件
这个文件包含了索引项在每个文档中出现的位置信息,你可以利用这些信息来参与对索引结果的排序。
表6显示了这个文件的结构
表6:
位置文件的结构
到目前为止我们介绍了Lucene中的主要的索引文件结构,希望能对你理解Lucene的物理的存储结构有所帮助。
回页首
总结
目前已经有非常多的知名的组织正在使用Lucene,比如,Lucene为Eclipse的帮助系统,麻省理工学院的OpenCourseWare提供了搜索功能。
通过阅读这篇文章,希望你能对Lucene的索引机制有所了解,并且你会发现利用Lucene创建索引是非常简单的事情。