(轉(zhuǎn))
本文首先介紹了Lucene的一些基本概念,然后開(kāi)發(fā)了一個(gè)應(yīng)用程序演示了利用Lucene建立索引并在該索引上進(jìn)行搜索的過(guò)程。
Lucene 是一個(gè)基于 Java 的全文信息檢索工具包,它不是一個(gè)完整的搜索應(yīng)用程序,而是為你的應(yīng)用程序提供索引和搜索功能。Lucene 目前是 Apache Jakarta 家族中的一個(gè)開(kāi)源項(xiàng)目。也是目前最為流行的基于 Java 開(kāi)源全文檢索工具包。
目前已經(jīng)有很多應(yīng)用程序的搜索功能是基于 Lucene 的,比如 Eclipse 的幫助系統(tǒng)的搜索功能。Lucene 能夠?yàn)槲谋绢愋偷臄?shù)據(jù)建立索引,所以你只要能把你要索引的數(shù)據(jù)格式轉(zhuǎn)化的文本的,Lucene 就能對(duì)你的文檔進(jìn)行索引和搜索。比如你要對(duì)一些 HTML 文檔,PDF 文檔進(jìn)行索引的話你就首先需要把 HTML 文檔和 PDF 文檔轉(zhuǎn)化成文本格式的,然后將轉(zhuǎn)化后的內(nèi)容交給 Lucene 進(jìn)行索引,然后把創(chuàng)建好的索引文件保存到磁盤(pán)或者內(nèi)存中,最后根據(jù)用戶輸入的查詢條件在索引文件上進(jìn)行查詢。不指定要索引的文檔的格式也使 Lucene 能夠幾乎適用于所有的搜索應(yīng)用程序。
圖 1 表示了搜索應(yīng)用程序和 Lucene 之間的關(guān)系,也反映了利用 Lucene 構(gòu)建搜索應(yīng)用程序的流程:
圖1. 搜索應(yīng)用程序和 Lucene 之間的關(guān)系

![]() ![]() |
索引是現(xiàn)代搜索引擎的核心,建立索引的過(guò)程就是把源數(shù)據(jù)處理成非常方便查詢的索引文件的過(guò)程。為什么索引這么重要呢,試想你現(xiàn)在要在大量的文檔中搜索含有某個(gè)關(guān)鍵詞的文檔,那么如果不建立索引的話你就需要把這些文檔順序的讀入內(nèi)存,然后檢查這個(gè)文章中是不是含有要查找的關(guān)鍵詞,這樣的話就會(huì)耗費(fèi)非常多的時(shí)間,想想搜索引擎可是在毫秒級(jí)的時(shí)間內(nèi)查找出要搜索的結(jié)果的。這就是由于建立了索引的原因,你可以把索引想象成這樣一種數(shù)據(jù)結(jié)構(gòu),他能夠使你快速的隨機(jī)訪問(wèn)存儲(chǔ)在索引中的關(guān)鍵詞,進(jìn)而找到該關(guān)鍵詞所關(guān)聯(lián)的文檔。Lucene 采用的是一種稱為反向索引(inverted index)的機(jī)制。反向索引就是說(shuō)我們維護(hù)了一個(gè)詞/短語(yǔ)表,對(duì)于這個(gè)表中的每個(gè)詞/短語(yǔ),都有一個(gè)鏈表描述了有哪些文檔包含了這個(gè)詞/短語(yǔ)。這樣在用戶輸入查詢條件的時(shí)候,就能非常快的得到搜索結(jié)果。我們將在本系列文章的第二部分詳細(xì)介紹 Lucene 的索引機(jī)制,由于 Lucene 提供了簡(jiǎn)單易用的 API,所以即使讀者剛開(kāi)始對(duì)全文本進(jìn)行索引的機(jī)制并不太了解,也可以非常容易的使用 Lucene 對(duì)你的文檔實(shí)現(xiàn)索引。
對(duì)文檔建立好索引后,就可以在這些索引上面進(jìn)行搜索了。搜索引擎首先會(huì)對(duì)搜索的關(guān)鍵詞進(jìn)行解析,然后再在建立好的索引上面進(jìn)行查找,最終返回和用戶輸入的關(guān)鍵詞相關(guān)聯(lián)的文檔。
![]() ![]() |
Lucene 軟件包的發(fā)布形式是一個(gè) JAR 文件,下面我們分析一下這個(gè) JAR 文件里面的主要的 JAVA 包,使讀者對(duì)之有個(gè)初步的了解。
Package: org.apache.lucene.document
這個(gè)包提供了一些為封裝要索引的文檔所需要的類,比如 Document, Field。這樣,每一個(gè)文檔最終被封裝成了一個(gè) Document 對(duì)象。
Package: org.apache.lucene.analysis
這個(gè)包主要功能是對(duì)文檔進(jìn)行分詞,因?yàn)槲臋n在建立索引之前必須要進(jìn)行分詞,所以這個(gè)包的作用可以看成是為建立索引做準(zhǔn)備工作。
Package: org.apache.lucene.index
這個(gè)包提供了一些類來(lái)協(xié)助創(chuàng)建索引以及對(duì)創(chuàng)建好的索引進(jìn)行更新。這里面有兩個(gè)基礎(chǔ)的類:IndexWriter 和 IndexReader,其中 IndexWriter 是用來(lái)創(chuàng)建索引并添加文檔到索引中的,IndexReader 是用來(lái)刪除索引中的文檔的。
Package: org.apache.lucene.search
這個(gè)包提供了對(duì)在建立好的索引上進(jìn)行搜索所需要的類。比如 IndexSearcher 和 Hits, IndexSearcher 定義了在指定的索引上進(jìn)行搜索的方法,Hits 用來(lái)保存搜索得到的結(jié)果。
![]() ![]() |
假設(shè)我們的電腦的目錄中含有很多文本文檔,我們需要查找哪些文檔含有某個(gè)關(guān)鍵詞。為了實(shí)現(xiàn)這種功能,我們首先利用 Lucene 對(duì)這個(gè)目錄中的文檔建立索引,然后在建立好的索引中搜索我們所要查找的文檔。通過(guò)這個(gè)例子讀者會(huì)對(duì)如何利用 Lucene 構(gòu)建自己的搜索應(yīng)用程序有個(gè)比較清楚的認(rèn)識(shí)。
![]() ![]() |
為了對(duì)文檔進(jìn)行索引,Lucene 提供了五個(gè)基礎(chǔ)的類,他們分別是 Document, Field, IndexWriter, Analyzer, Directory。下面我們分別介紹一下這五個(gè)類的用途:
Document
Document 是用來(lái)描述文檔的,這里的文檔可以指一個(gè) HTML 頁(yè)面,一封電子郵件,或者是一個(gè)文本文件。一個(gè) Document 對(duì)象由多個(gè) Field 對(duì)象組成的。可以把一個(gè) Document 對(duì)象想象成數(shù)據(jù)庫(kù)中的一個(gè)記錄,而每個(gè) Field 對(duì)象就是記錄的一個(gè)字段。
Field
Field 對(duì)象是用來(lái)描述一個(gè)文檔的某個(gè)屬性的,比如一封電子郵件的標(biāo)題和內(nèi)容可以用兩個(gè) Field 對(duì)象分別描述。
Analyzer
在一個(gè)文檔被索引之前,首先需要對(duì)文檔內(nèi)容進(jìn)行分詞處理,這部分工作就是由 Analyzer 來(lái)做的。Analyzer 類是一個(gè)抽象類,它有多個(gè)實(shí)現(xiàn)。針對(duì)不同的語(yǔ)言和應(yīng)用需要選擇適合的 Analyzer。Analyzer 把分詞后的內(nèi)容交給 IndexWriter 來(lái)建立索引。
IndexWriter
IndexWriter 是 Lucene 用來(lái)創(chuàng)建索引的一個(gè)核心的類,他的作用是把一個(gè)個(gè)的 Document 對(duì)象加到索引中來(lái)。
Directory
這個(gè)類代表了 Lucene 的索引的存儲(chǔ)的位置,這是一個(gè)抽象類,它目前有兩個(gè)實(shí)現(xiàn),第一個(gè)是 FSDirectory,它表示一個(gè)存儲(chǔ)在文件系統(tǒng)中的索引的位置。第二個(gè)是 RAMDirectory,它表示一個(gè)存儲(chǔ)在內(nèi)存當(dāng)中的索引的位置。
熟悉了建立索引所需要的這些類后,我們就開(kāi)始對(duì)某個(gè)目錄下面的文本文件建立索引了,清單1給出了對(duì)某個(gè)目錄下的文本文件建立索引的源代碼。
清單 1. 對(duì)文本文件建立索引
package TestLucene;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
import java.util.Date;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
/**
* This class demonstrate the process of creating index with Lucene
* for text files
*/
public class TxtFileIndexer {
public static void main(String[] args) throws Exception{
//indexDir is the directory that hosts Lucene's index files
File indexDir = new File("D:\\luceneIndex");
//dataDir is the directory that hosts the text files that to be indexed
File dataDir = new File("D:\\luceneData");
Analyzer luceneAnalyzer = new StandardAnalyzer();
File[] dataFiles = dataDir.listFiles();
IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true);
long startTime = new Date().getTime();
for(int i = 0; i < dataFiles.length; i++){
if(dataFiles[i].isFile() && dataFiles[i].getName().endsWith(".txt")){
System.out.println("Indexing file " + dataFiles[i].getCanonicalPath());
Document document = new Document();
Reader txtReader = new FileReader(dataFiles[i]);
document.add(new Field("path", dataFiles[i].getCanonicalPath(),
|
在清單1中,我們注意到類 IndexWriter 的構(gòu)造函數(shù)需要三個(gè)參數(shù),第一個(gè)參數(shù)指定了所創(chuàng)建的索引要存放的位置,他可以是一個(gè) File 對(duì)象,也可以是一個(gè) FSDirectory 對(duì)象或者 RAMDirectory 對(duì)象。第二個(gè)參數(shù)指定了 Analyzer 類的一個(gè)實(shí)現(xiàn),也就是指定這個(gè)索引是用哪個(gè)分詞器對(duì)文擋內(nèi)容進(jìn)行分詞。第三個(gè)參數(shù)是一個(gè)布爾型的變量,如果為 true 的話就代表創(chuàng)建一個(gè)新的索引,為 false 的話就代表在原來(lái)索引的基礎(chǔ)上進(jìn)行操作。接著程序遍歷了目錄下面的所有文本文檔,并為每一個(gè)文本文檔創(chuàng)建了一個(gè) Document 對(duì)象。然后把文本文檔的兩個(gè)屬性:路徑和內(nèi)容加入到了兩個(gè) Field 對(duì)象中,接著在把這兩個(gè) Field 對(duì)象加入到 Document 對(duì)象中,最后把這個(gè)文檔用 IndexWriter 類的 add 方法加入到索引中去。這樣我們便完成了索引的創(chuàng)建。接下來(lái)我們進(jìn)入在建立好的索引上進(jìn)行搜索的部分。
![]() ![]() |
利用Lucene進(jìn)行搜索就像建立索引一樣也是非常方便的。在上面一部分中,我們已經(jīng)為一個(gè)目錄下的文本文檔建立好了索引,現(xiàn)在我們就要在這個(gè)索引上進(jìn)行搜索以找到包含某個(gè)關(guān)鍵詞或短語(yǔ)的文檔。Lucene提供了幾個(gè)基礎(chǔ)的類來(lái)完成這個(gè)過(guò)程,它們分別是呢IndexSearcher, Term, Query, TermQuery, Hits. 下面我們分別介紹這幾個(gè)類的功能。
Query
這是一個(gè)抽象類,他有多個(gè)實(shí)現(xiàn),比如TermQuery, BooleanQuery, PrefixQuery. 這個(gè)類的目的是把用戶輸入的查詢字符串封裝成Lucene能夠識(shí)別的Query。
Term
Term是搜索的基本單位,一個(gè)Term對(duì)象有兩個(gè)String類型的域組成。生成一個(gè)Term對(duì)象可以有如下一條語(yǔ)句來(lái)完成:Term term = new Term(“fieldName”,”queryWord”); 其中第一個(gè)參數(shù)代表了要在文檔的哪一個(gè)Field上進(jìn)行查找,第二個(gè)參數(shù)代表了要查詢的關(guān)鍵詞。
TermQuery
TermQuery是抽象類Query的一個(gè)子類,它同時(shí)也是Lucene支持的最為基本的一個(gè)查詢類。生成一個(gè)TermQuery對(duì)象由如下語(yǔ)句完成: TermQuery termQuery = new TermQuery(new Term(“fieldName”,”queryWord”)); 它的構(gòu)造函數(shù)只接受一個(gè)參數(shù),那就是一個(gè)Term對(duì)象。
IndexSearcher
IndexSearcher是用來(lái)在建立好的索引上進(jìn)行搜索的。它只能以只讀的方式打開(kāi)一個(gè)索引,所以可以有多個(gè)IndexSearcher的實(shí)例在一個(gè)索引上進(jìn)行操作。
Hits
Hits是用來(lái)保存搜索的結(jié)果的。
介紹完這些搜索所必須的類之后,我們就開(kāi)始在之前所建立的索引上進(jìn)行搜索了,清單2給出了完成搜索功能所需要的代碼。
清單2 :在建立好的索引上進(jìn)行搜索
package TestLucene;
import java.io.File;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.FSDirectory;
/**
* This class is used to demonstrate the
* process of searching on an existing
* Lucene index
*
*/
public class TxtFileSearcher {
public static void main(String[] args) throws Exception{
String queryStr = "lucene";
//This is the directory that hosts the Lucene index
File indexDir = new File("D:\\luceneIndex");
FSDirectory directory = FSDirectory.getDirectory(indexDir,false);
IndexSearcher searcher = new IndexSearcher(directory);
if(!indexDir.exists()){
System.out.println("The Lucene index is not exist");
return;
}
Term term = new Term("contents",queryStr.toLowerCase());
TermQuery luceneQuery = new TermQuery(term);
Hits hits = searcher.search(luceneQuery);
for(int i = 0; i < hits.length(); i++){
Document document = hits.doc(i);
System.out.println("File: " + document.get("path"));
}
}
}
|
在清單2中,類IndexSearcher的構(gòu)造函數(shù)接受一個(gè)類型為Directory的對(duì)象,Directory是一個(gè)抽象類,它目前有兩個(gè)子類:FSDirctory和RAMDirectory. 我們的程序中傳入了一個(gè)FSDirctory對(duì)象作為其參數(shù),代表了一個(gè)存儲(chǔ)在磁盤(pán)上的索引的位置。構(gòu)造函數(shù)執(zhí)行完成后,代表了這個(gè)IndexSearcher以只讀的方式打開(kāi)了一個(gè)索引。然后我們程序構(gòu)造了一個(gè)Term對(duì)象,通過(guò)這個(gè)Term對(duì)象,我們指定了要在文檔的內(nèi)容中搜索包含關(guān)鍵詞”lucene”的文檔。接著利用這個(gè)Term對(duì)象構(gòu)造出TermQuery對(duì)象并把這個(gè)TermQuery對(duì)象傳入到IndexSearcher的search方法中進(jìn)行查詢,返回的結(jié)果保存在Hits對(duì)象中。最后我們用了一個(gè)循環(huán)語(yǔ)句把搜索到的文檔的路徑都打印了出來(lái)。好了,我們的搜索應(yīng)用程序已經(jīng)開(kāi)發(fā)完畢,怎么樣,利用Lucene開(kāi)發(fā)搜索應(yīng)用程序是不是很簡(jiǎn)單。
![]() ![]() |
本文首先介紹了 Lucene 的一些基本概念,然后開(kāi)發(fā)了一個(gè)應(yīng)用程序演示了利用 Lucene 建立索引并在該索引上進(jìn)行搜索的過(guò)程。希望本文能夠?yàn)閷W(xué)習(xí) Lucene 的讀者提供幫助。
圖一顯示了 Lucene 的索引機(jī)制的架構(gòu)。Lucene 使用各種解析器對(duì)各種不同類型的文檔進(jìn)行解析。比如對(duì)于 HTML 文檔,HTML 解析器會(huì)做一些預(yù)處理的工作,比如過(guò)濾文檔中的 HTML 標(biāo)簽等等。HTML 解析器的輸出的是文本內(nèi)容,接著 Lucene 的分詞器(Analyzer)從文本內(nèi)容中提取出索引項(xiàng)以及相關(guān)信息,比如索引項(xiàng)的出現(xiàn)頻率。接著 Lucene 的分詞器把這些信息寫(xiě)到索引文件中。
![]() ![]() |
接下來(lái)我將一步一步的來(lái)演示如何利用 Lucene 為你的文檔創(chuàng)建索引。只要你能將要索引的文件轉(zhuǎn)化成文本格式,Lucene 就能為你的文檔建立索引。比如,如果你想為 HTML 文檔或者 PDF 文檔建立索引,那么首先你就需要從這些文檔中提取出文本信息,然后把文本信息交給 Lucene 建立索引。我們接下來(lái)的例子用來(lái)演示如何利用 Lucene 為后綴名為 txt 的文件建立索引。
1. 準(zhǔn)備文本文件
首先把一些以 txt 為后綴名的文本文件放到一個(gè)目錄中,比如在 Windows 平臺(tái)上,你可以放到 C:\\files_to_index 下面。
2. 創(chuàng)建索引
清單1是為我們所準(zhǔn)備的文檔創(chuàng)建索引的代碼。
package lucene.index; import java.io.File; import java.io.FileReader; import java.io.Reader; import java.util.Date; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexWriter; /** * This class demonstrates the process of creating an index with Lucene * for text files in a directory. */ public class TextFileIndexer { public static void main(String[] args) throws Exception{ //fileDir is the directory that contains the text files to be indexed File fileDir = new File("C:\\files_to_index "); //indexDir is the directory that hosts Lucene's index files File indexDir = new File("C:\\luceneIndex"); Analyzer luceneAnalyzer = new StandardAnalyzer(); IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true); File[] textFiles = fileDir.listFiles(); long startTime = new Date().getTime(); //Add documents to the index for(int i = 0; i < textFiles.length; i++){ if(textFiles[i].isFile() >> textFiles[i].getName().endsWith(".txt")){ System.out.println("File " + textFiles[i].getCanonicalPath() + " is being indexed"); Reader textReader = new FileReader(textFiles[i]); Document document = new Document(); document.add(Field.Text("content",textReader)); document.add(Field.Text("path",textFiles[i].getPath())); indexWriter.addDocument(document); } } indexWriter.optimize(); indexWriter.close(); long endTime = new Date().getTime(); System.out.println("It took " + (endTime - startTime) + " milliseconds to create an index for the files in the directory " + fileDir.getPath()); } } |
正如清單1所示,你可以利用 Lucene 非常方便的為文檔創(chuàng)建索引。接下來(lái)我們分析一下清單1中的比較關(guān)鍵的代碼,我們先從下面的一條語(yǔ)句開(kāi)始看起。
Analyzer luceneAnalyzer = new StandardAnalyzer(); |
這條語(yǔ)句創(chuàng)建了類 StandardAnalyzer 的一個(gè)實(shí)例,這個(gè)類是用來(lái)從文本中提取出索引項(xiàng)的。它只是抽象類 Analyzer 的其中一個(gè)實(shí)現(xiàn)。Analyzer 也有一些其它的子類,比如 SimpleAnalyzer 等。
我們接著看另外一條語(yǔ)句:
IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true); |
這條語(yǔ)句創(chuàng)建了類 IndexWriter 的一個(gè)實(shí)例,該類也是 Lucene 索引機(jī)制里面的一個(gè)關(guān)鍵類。這個(gè)類能創(chuàng)建一個(gè)新的索引或者打開(kāi)一個(gè)已存在的索引并為該所引添加文檔。我們注意到該類的構(gòu)造函數(shù)接受三個(gè)參數(shù),第一個(gè)參數(shù)指定了存儲(chǔ)索引文件的路徑。第二個(gè)參數(shù)指定了在索引過(guò)程中使用什么樣的分詞器。最后一個(gè)參數(shù)是個(gè)布爾變量,如果值為真,那么就表示要?jiǎng)?chuàng)建一個(gè)新的索引,如果值為假,就表示打開(kāi)一個(gè)已經(jīng)存在的索引。
接下來(lái)的代碼演示了如何添加一個(gè)文檔到索引文件中。
Document document = new Document(); document.add(Field.Text("content",textReader)); document.add(Field.Text("path",textFiles[i].getPath())); indexWriter.addDocument(document); |
首先第一行創(chuàng)建了類 Document 的一個(gè)實(shí)例,它由一個(gè)或者多個(gè)的域(Field)組成。你可以把這個(gè)類想象成代表了一個(gè)實(shí)際的文檔,比如一個(gè) HTML 頁(yè)面,一個(gè) PDF 文檔,或者一個(gè)文本文件。而類 Document 中的域一般就是實(shí)際文檔的一些屬性。比如對(duì)于一個(gè) HTML 頁(yè)面,它的域可能包括標(biāo)題,內(nèi)容,URL 等。我們可以用不同類型的 Field 來(lái)控制文檔的哪些內(nèi)容應(yīng)該索引,哪些內(nèi)容應(yīng)該存儲(chǔ)。如果想獲取更多的關(guān)于 Lucene 的域的信息,可以參考 Lucene 的幫助文檔。代碼的第二行和第三行為文檔添加了兩個(gè)域,每個(gè)域包含兩個(gè)屬性,分別是域的名字和域的內(nèi)容。在我們的例子中兩個(gè)域的名字分別是"content"和"path"。分別存儲(chǔ)了我們需要索引的文本文件的內(nèi)容和路徑。最后一行把準(zhǔn)備好的文檔添加到了索引當(dāng)中。
當(dāng)我們把文檔添加到索引中后,不要忘記關(guān)閉索引,這樣才保證 Lucene 把添加的文檔寫(xiě)回到硬盤(pán)上。下面的一句代碼演示了如何關(guān)閉索引。
indexWriter.close(); |
利用清單1中的代碼,你就可以成功的將文本文檔添加到索引中去。接下來(lái)我們看看對(duì)索引進(jìn)行的另外一種重要的操作,從索引中刪除文檔。
![]() ![]() |
類IndexReader負(fù)責(zé)從一個(gè)已經(jīng)存在的索引中刪除文檔,如清單2所示。
File indexDir = new File("C:\\luceneIndex"); IndexReader ir = IndexReader.open(indexDir); ir.delete(1); ir.delete(new Term("path","C:\\file_to_index\lucene.txt")); ir.close(); |
在清單2中,第二行用靜態(tài)方法 IndexReader.open(indexDir) 初始化了類 IndexReader 的一個(gè)實(shí)例,這個(gè)方法的參數(shù)指定了索引的存儲(chǔ)路徑。類 IndexReader 提供了兩種方法去刪除一個(gè)文檔,如程序中的第三行和第四行所示。第三行利用文檔的編號(hào)來(lái)刪除文檔。每個(gè)文檔都有一個(gè)系統(tǒng)自動(dòng)生成的編號(hào)。第四行刪除了路徑為"C:\\file_to_index\lucene.txt"的文檔。你可以通過(guò)指定文件路徑來(lái)方便的刪除一個(gè)文檔。值得注意的是雖然利用上述代碼刪除文檔使得該文檔不能被檢索到,但是并沒(méi)有物理上刪除該文檔。Lucene 只是通過(guò)一個(gè)后綴名為 .delete 的文件來(lái)標(biāo)記哪些文檔已經(jīng)被刪除。既然沒(méi)有物理上刪除,我們可以方便的把這些標(biāo)記為刪除的文檔恢復(fù)過(guò)來(lái),如清單 3 所示,首先打開(kāi)一個(gè)索引,然后調(diào)用方法 ir.undeleteAll() 來(lái)完成恢復(fù)工作。
File indexDir = new File("C:\\luceneIndex"); IndexReader ir = IndexReader.open(indexDir); ir.undeleteAll(); ir.close(); |
你現(xiàn)在也許想知道如何物理上刪除索引中的文檔,方法也非常簡(jiǎn)單。清單 4 演示了這個(gè)過(guò)程。
File indexDir = new File("C:\\luceneIndex"); Analyzer luceneAnalyzer = new StandardAnalyzer(); IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,false); indexWriter.optimize(); indexWriter.close(); |
在清單 4 中,第三行創(chuàng)建了類 IndexWriter 的一個(gè)實(shí)例,并且打開(kāi)了一個(gè)已經(jīng)存在的索引。第 4 行對(duì)索引進(jìn)行清理,清理過(guò)程中將把所有標(biāo)記為刪除的文檔物理刪除。
Lucene 沒(méi)有直接提供方法對(duì)文檔進(jìn)行更新,如果你需要更新一個(gè)文檔,那么你首先需要把這個(gè)文檔從索引中刪除,然后把新版本的文檔加入到索引中去。
![]() ![]() |
利用 Lucene,在創(chuàng)建索引的工程中你可以充分利用機(jī)器的硬件資源來(lái)提高索引的效率。當(dāng)你需要索引大量的文件時(shí),你會(huì)注意到索引過(guò)程的瓶頸是在往磁盤(pán)上寫(xiě)索引文件的過(guò)程中。為了解決這個(gè)問(wèn)題, Lucene 在內(nèi)存中持有一塊緩沖區(qū)。但我們?nèi)绾慰刂?Lucene 的緩沖區(qū)呢?幸運(yùn)的是,Lucene 的類 IndexWriter 提供了三個(gè)參數(shù)用來(lái)調(diào)整緩沖區(qū)的大小以及往磁盤(pán)上寫(xiě)索引文件的頻率。
1.合并因子(mergeFactor)
這個(gè)參數(shù)決定了在 Lucene 的一個(gè)索引塊中可以存放多少文檔以及把磁盤(pán)上的索引塊合并成一個(gè)大的索引塊的頻率。比如,如果合并因子的值是 10,那么當(dāng)內(nèi)存中的文檔數(shù)達(dá)到 10 的時(shí)候所有的文檔都必須寫(xiě)到磁盤(pán)上的一個(gè)新的索引塊中。并且,如果磁盤(pán)上的索引塊的隔數(shù)達(dá)到 10 的話,這 10 個(gè)索引塊會(huì)被合并成一個(gè)新的索引塊。這個(gè)參數(shù)的默認(rèn)值是 10,如果需要索引的文檔數(shù)非常多的話這個(gè)值將是非常不合適的。對(duì)批處理的索引來(lái)講,為這個(gè)參數(shù)賦一個(gè)比較大的值會(huì)得到比較好的索引效果。
2.最小合并文檔數(shù)
這個(gè)參數(shù)也會(huì)影響索引的性能。它決定了內(nèi)存中的文檔數(shù)至少達(dá)到多少才能將它們寫(xiě)回磁盤(pán)。這個(gè)參數(shù)的默認(rèn)值是10,如果你有足夠的內(nèi)存,那么將這個(gè)值盡量設(shè)的比較大一些將會(huì)顯著的提高索引性能。
3.最大合并文檔數(shù)
這個(gè)參數(shù)決定了一個(gè)索引塊中的最大的文檔數(shù)。它的默認(rèn)值是 Integer.MAX_VALUE,將這個(gè)參數(shù)設(shè)置為比較大的值可以提高索引效率和檢索速度,由于該參數(shù)的默認(rèn)值是整型的最大值,所以我們一般不需要改動(dòng)這個(gè)參數(shù)。
清單 5 列出了這個(gè)三個(gè)參數(shù)用法,清單 5 和清單 1 非常相似,除了清單 5 中會(huì)設(shè)置剛才提到的三個(gè)參數(shù)。
/** * This class demonstrates how to improve the indexing performance * by adjusting the parameters provided by IndexWriter. */ public class AdvancedTextFileIndexer { public static void main(String[] args) throws Exception{ //fileDir is the directory that contains the text files to be indexed File fileDir = new File("C:\\files_to_index"); //indexDir is the directory that hosts Lucene's index files File indexDir = new File("C:\\luceneIndex"); Analyzer luceneAnalyzer = new StandardAnalyzer(); File[] textFiles = fileDir.listFiles(); long startTime = new Date().getTime(); int mergeFactor = 10; int minMergeDocs = 10; int maxMergeDocs = Integer.MAX_VALUE; IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true); indexWriter.mergeFactor = mergeFactor; indexWriter.minMergeDocs = minMergeDocs; indexWriter.maxMergeDocs = maxMergeDocs; //Add documents to the index for(int i = 0; i < textFiles.length; i++){ if(textFiles[i].isFile() >> textFiles[i].getName().endsWith(".txt")){ Reader textReader = new FileReader(textFiles[i]); Document document = new Document(); document.add(Field.Text("content",textReader)); document.add(Field.Keyword("path",textFiles[i].getPath())); indexWriter.addDocument(document); } } indexWriter.optimize(); indexWriter.close(); long endTime = new Date().getTime(); System.out.println("MergeFactor: " + indexWriter.mergeFactor); System.out.println("MinMergeDocs: " + indexWriter.minMergeDocs); System.out.println("MaxMergeDocs: " + indexWriter.maxMergeDocs); System.out.println("Document number: " + textFiles.length); System.out.println("Time consumed: " + (endTime - startTime) + " milliseconds"); } } |
通過(guò)這個(gè)例子,我們注意到在調(diào)整緩沖區(qū)的大小以及寫(xiě)磁盤(pán)的頻率上面 Lucene 給我們提供了非常大的靈活性。現(xiàn)在我們來(lái)看一下代碼中的關(guān)鍵語(yǔ)句。如下的代碼首先創(chuàng)建了類 IndexWriter 的一個(gè)實(shí)例,然后對(duì)它的三個(gè)參數(shù)進(jìn)行賦值。
int mergeFactor = 10; int minMergeDocs = 10; int maxMergeDocs = Integer.MAX_VALUE; IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true); indexWriter.mergeFactor = mergeFactor; indexWriter.minMergeDocs = minMergeDocs; indexWriter.maxMergeDocs = maxMergeDocs; |
下面我們來(lái)看一下這三個(gè)參數(shù)取不同的值對(duì)索引時(shí)間的影響,注意參數(shù)值的不同和索引之間的關(guān)系。我們?yōu)檫@個(gè)實(shí)驗(yàn)準(zhǔn)備了 10000 個(gè)測(cè)試文檔。表 1 顯示了測(cè)試結(jié)果。
通過(guò)表 1,你可以清楚地看到三個(gè)參數(shù)對(duì)索引時(shí)間的影響。在實(shí)踐中,你會(huì)經(jīng)常的改變合并因子和最小合并文檔數(shù)的值來(lái)提高索引性能。只要你有足夠大的內(nèi)存,你可以為合并因子和最小合并文檔數(shù)這兩個(gè)參數(shù)賦盡量大的值以提高索引效率,另外我們一般無(wú)需更改最大合并文檔數(shù)這個(gè)參數(shù)的值,因?yàn)橄到y(tǒng)已經(jīng)默認(rèn)將它設(shè)置成了最大。
![]() ![]() |
在分析 Lucene 的索引文件結(jié)構(gòu)之前,我們先要理解反向索引(Inverted index)這個(gè)概念,反向索引是一種以索引項(xiàng)為中心來(lái)組織文檔的方式,每個(gè)索引項(xiàng)指向一個(gè)文檔序列,這個(gè)序列中的文檔都包含該索引項(xiàng)。相反,在正向索引中,文檔占據(jù)了中心的位置,每個(gè)文檔指向了一個(gè)它所包含的索引項(xiàng)的序列。你可以利用反向索引輕松的找到那些文檔包含了特定的索引項(xiàng)。Lucene正是使用了反向索引作為其基本的索引結(jié)構(gòu)。
![]() ![]() |
在Lucene 中有索引塊的概念,每個(gè)索引塊包含了一定數(shù)目的文檔。我們能夠?qū)为?dú)的索引塊進(jìn)行檢索。圖 2 顯示了 Lucene 索引結(jié)構(gòu)的邏輯視圖。索引塊的個(gè)數(shù)由索引的文檔的總數(shù)以及每個(gè)索引塊所能包含的最大文檔數(shù)來(lái)決定。
![]() ![]() |
下面的部分將會(huì)分析Lucene中的主要的索引文件,可能分析有些索引文件的時(shí)候沒(méi)有包含文件的所有的字段,但不會(huì)影響到對(duì)索引文件的理解。
1.索引塊文件
這個(gè)文件包含了索引中的索引塊信息,這個(gè)文件包含了每個(gè)索引塊的名字以及大小等信息。表 2 顯示了這個(gè)文件的結(jié)構(gòu)信息。
2.域信息文件
我們知道,索引中的文檔由一個(gè)或者多個(gè)域組成,這個(gè)文件包含了每個(gè)索引塊中的域的信息。表 3 顯示了這個(gè)文件的結(jié)構(gòu)。
3.索引項(xiàng)信息文件
這是索引文件里面最核心的一個(gè)文件,它存儲(chǔ)了所有的索引項(xiàng)的值以及相關(guān)信息,并且以索引項(xiàng)來(lái)排序。表 4 顯示了這個(gè)文件的結(jié)構(gòu)。
4.頻率文件
這個(gè)文件包含了包含索引項(xiàng)的文檔的列表,以及索引項(xiàng)在每個(gè)文檔中出現(xiàn)的頻率信息。如果Lucene在索引項(xiàng)信息文件中發(fā)現(xiàn)有索引項(xiàng)和搜索詞相匹配。那么 Lucene 就會(huì)在頻率文件中找有哪些文件包含了該索引項(xiàng)。表5顯示了這個(gè)文件的一個(gè)大致的結(jié)構(gòu),并沒(méi)有包含這個(gè)文件的所有字段。
5.位置文件
這個(gè)文件包含了索引項(xiàng)在每個(gè)文檔中出現(xiàn)的位置信息,你可以利用這些信息來(lái)參與對(duì)索引結(jié)果的排序。表 6 顯示了這個(gè)文件的結(jié)構(gòu)
到目前為止我們介紹了 Lucene 中的主要的索引文件結(jié)構(gòu),希望能對(duì)你理解 Lucene 的物理的存儲(chǔ)結(jié)構(gòu)有所幫助。
![]() ![]() |
目前已經(jīng)有非常多的知名的組織正在使用 Lucene,比如,Lucene 為 Eclipse 的幫助系統(tǒng),麻省理工學(xué)院的 OpenCourseWare 提供了搜索功能。通過(guò)閱讀這篇文章,希望你能對(duì) Lucene 的索引機(jī)制有所了解,并且你會(huì)發(fā)現(xiàn)利用 Lucene 創(chuàng)建索引是非常簡(jiǎn)單的事情。
學(xué)習(xí)
- 您可以參閱本文在 developerWorks 全球站點(diǎn)上的 英文原文 。
- 實(shí)戰(zhàn) Lucene: 初識(shí) Lucene 介紹了 Lucene 的一些基本概念,然后開(kāi)發(fā)了一個(gè)應(yīng)用程序演示了利用 Lucene 建立索引并在該索引上進(jìn)行搜索的過(guò)程。
- Parsing, indexing, and searching XML with Digester and Lucene 是 Otis Gospodnetic 在 developerWorks 上發(fā)表的一篇關(guān)于利用 Lucene 和 Digester 來(lái)操作 XML 文檔的文章。
- IBM Search and Index APIs (SIAPI) for WebSphere Information Integrator OmniFind Edition 是 Srinivas Varma Chitiveli 在 developerWorks 上發(fā)表的一篇關(guān)于如何用 SIAPI 來(lái)構(gòu)建搜索解決方案的文章。
- Lucene的官方網(wǎng)站:上面有大量的 Lucene 幫助文檔。
- 一個(gè)關(guān)于 Lucene 的演講:是由 Lucene 最初的作者 Doug Cutting 在 Pisa 大學(xué)所作。
- 現(xiàn)代信息檢索是由 Ricardo Baeza-Yates 和 Berthier Ribeiro-Neto 所寫(xiě)的關(guān)于信息檢索方面的一本著作。
- developerWorks Web Architecture 專區(qū):上面有很多關(guān)于如何構(gòu)建網(wǎng)站的技術(shù)文章。
獲得產(chǎn)品和技術(shù)
- 下載 Lucene 最新版本。