Lucene 簡單手記

Lucene 簡單手記

什麼是全文檢索與全文檢索系統?

全文檢索是指計算機索引程序通過掃描文章中的每一個詞,對每一個詞建立一個索引,指明該詞在文章中出現的次數和位置,當用戶查詢時,檢索程序就根據事先建立的索引進行查找,並將查找的結果反饋給用戶的檢索方式。這個過程類似於通過字典中的檢索字表查字的過程。

 

全文檢索的方法主要分爲按字檢索和按詞檢索兩種。按字檢索是指對於文章中的每一個字都建立索引,檢索時將詞分解爲字的組合。對於各種不同的語言而言,字有不同的含義,比如英文中字與詞實際上是合一的,而中文中字與詞有很大分別。按詞檢索指對文章中的詞,即語義單位建立索引,檢索時按詞檢索,並且可以處理同義項等。

 

全文檢索系統是按照全文檢索理論建立起來的用於提供全文檢索服務的軟件系統。一般來說,全文檢索需要具備建立索引和提供查詢的基本功能,此外現代的全文檢索系統還需要具有方便的用戶接口、面向WWW[1]的開發接口、二次應用開發接口等等。功能上,全文檢索系統核心具有建立索引、處理查詢返回結果集、增加索引、優化索引結構等等功能,外圍則由各種不同應用具有的功能組成。結構上,全文檢索系統核心具有索引引擎、查詢引擎、文本分析引擎、對外接口等等,加上各種外圍應用系統等等共同構成了全文檢索系統。

 

什麼是Lucene?

Lucene是apache軟件基金會jakarta項目組的一個子項目,是一個開放源代碼的全文檢索引擎工具包,即它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文本分析引擎(英文與德文兩種西方語言)。Lucene的目的是爲軟件開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能,或者是以此爲基礎建立起完整的全文檢索引擎。

 

Lucene的原作者是Doug Cutting,他是一位資深全文索引/檢索專家,曾經是V-Twin搜索引擎的主要開發者,後在Excite擔任高級系統架構設計師,目前從事於一些Internet底層架構的研究。早先發布在作者自己的http://www.lucene.com/,後來發佈在SourceForge,2001年年底成爲apache軟件基金會jakarta的一個子項目:http://jakarta.apache.org/lucene/

 

Lucene作爲一個全文檢索引擎,其具有如下突出的優點:

(1)索引文件格式獨立於應用平臺。Lucene定義了一套以8位字節爲基礎的索引文件格式,使得兼容系統或者不同平臺的應用能夠共享建立的索引文件。

(2)在傳統全文檢索引擎的倒排索引的基礎上,實現了分塊索引,能夠針對新的文件建立小文件索引,提升索引速度。然後通過與原有索引的合併,達到優化的目的。

(3)優秀的面向對象的系統架構,使得對於Lucene擴展的學習難度降低,方便擴充新功能。

(4)設計了獨立於語言和文件格式的文本分析接口,索引器通過接受Token流完成索引文件的創立,用戶擴展新的語言和文件格式,只需要實現文本分析的接口。

(5)已經默認實現了一套強大的查詢引擎,用戶無需自己編寫代碼即使系統可獲得強大的查詢能力,Lucene的查詢實現中默認實現了布爾操作、模糊查詢(Fuzzy Search)、分組查詢等等。

 

面對已經存在的商業全文檢索引擎,Lucene也具有相當的優勢:

首先,它的開發源代碼發行方式(遵守Apache Software License),在此基礎上程序員不僅僅可以充分的利用Lucene所提供的強大功能,而且可以深入細緻的學習到全文檢索引擎製作技術和麪相對象編程的實踐,進而在此基礎上根據應用的實際情況編寫出更好的更適合當前應用的全文檢索引擎。在這一點上,商業軟件的靈活性遠遠不及Lucene。

其次,Lucene秉承了開放源代碼一貫的架構優良的優勢,設計了一個合理而極具擴充能力的面向對象架構,程序員可以在Lucene的基礎上擴充各種功能,比如擴充中文處理能力,從文本擴充到HTML、PDF等等文本格式的處理,編寫這些擴展的功能不僅僅不復雜,而且由於Lucene恰當合理的對系統設備做了程序上的抽象,擴展的功能也能輕易的達到跨平臺的能力。

最後,轉移到apache軟件基金會後,藉助於apache軟件基金會的網絡平臺,程序員可以方便的和開發者、其它程序員交流,促成資源的共享,甚至直接獲得已經編寫完備的擴充功能。最後,雖然Lucene使用Java語言寫成,但是開放源代碼社區的程序員正在不懈的將之使用各種傳統語言實現(例如.net framework),在遵守Lucene索引文件格式的基礎上,使得Lucene能夠運行在各種各樣的平臺上,系統管理員可以根據當前的平臺適合的語言來合理的選。

 

索引和搜索的關係

索引是現代搜索引擎的核心,建立索引的過程就是把源數據處理成非常方便查詢的索引文件的過程。爲什麼索引這麼重要呢,試想你現在要在大量的文檔中搜索含有某個關鍵詞的文檔,那麼如果不建立索引的話你就需要把這些文檔順序的讀入內存,然後檢查這個文章中是不是含有要查找的關鍵詞,這樣的話就會耗費非常多的時間,想想搜索引擎可是在毫秒級的時間內查找出要搜索的結果的。這就是由於建立了索引的原因,你可以把索引想象成這樣一種數據結構,他能夠使你快速的隨機訪問存儲在索引中的關鍵詞,進而找到該關鍵詞所關聯的文檔。Lucene 採用的是一種稱爲反向索引(inverted index)的機制。反向索引就是說我們維護了一個詞/短語表,對於這個表中的每個詞/短語,都有一個鏈表描述了有哪些文檔包含了這個詞/短語。這樣在用戶輸入查詢條件的時候,就能非常快的得到搜索結果。我們將在本系列文章的第二部分詳細介紹 Lucene 的索引機制,由於 Lucene 提供了簡單易用的 API,所以即使讀者剛開始對全文本進行索引的機制並不太瞭解,也可以非常容易的使用 Lucene 對你的文檔實現索引。

對文檔建立好索引後,就可以在這些索引上面進行搜索了。搜索引擎首先會對搜索的關鍵詞進行解析,然後再在建立好的索引上面進行查找,最終返回和用戶輸入的關鍵詞相關聯的文檔。

 

Lucene 軟件包分析

Package: org.apache.lucene.document

這個包提供了一些爲封裝要索引的文檔所需要的類,比如 Document, Field。這樣,每一個文檔最終被封裝成了一個 Document 對象。

Package: org.apache.lucene.analysis

這個包主要功能是對文檔進行分詞,因爲文檔在建立索引之前必須要進行分詞,所以這個包的作用可以看成是爲建立索引做準備工作。

Package: org.apache.lucene.index

這個包提供了一些類來協助創建索引以及對創建好的索引進行更新。這裏面有兩個基礎的類:IndexWriter 和 IndexReader,其中 IndexWriter 是用來創建索引並添加文檔到索引中的,IndexReader 是用來刪除索引中的文檔的。

Package: org.apache.lucene.search

這個包提供了對在建立好的索引上進行搜索所需要的類。比如 IndexSearcher 和 Hits, IndexSearcher 定義了在指定的索引上進行搜索的方法,Hits 用來保存搜索得到的結果

Lucene包結構功能表

包名

功能

org.apache.lucene.analysis

語言分析器,主要用於的切詞,支持中文主要是擴展此類

org.apache.lucene.document

索引存儲時的文檔結構管理,類似於關係型數據庫的表結構

org.apache.lucene.index

索引管理,包括索引建立、刪除等

org.apache.lucene.queryParser

查詢分析器,實現查詢關鍵詞間的運算,如與、或、非等

org.apache.lucene.search

檢索管理,根據查詢條件,檢索得到結果

org.apache.lucene.store

數據存儲管理,主要包括一些底層的I/O操作

org.apache.lucene.util

一些公用類

 

一個簡單的搜索應用程序

假設我們的電腦的目錄中含有很多文本文檔,我們需要查找哪些文檔含有某個關鍵詞。爲了實現這種功能,我們首先利用

Lucene 對這個目錄中的文檔建立索引,然後在建立好的索引中搜索我們所要查找的文檔。通過這個例子讀者會對如何利用

Lucene 構建自己的搜索應用程序有個比較清楚的認識。

 

建立索引

爲了對文檔進行索引,Lucene 提供了五個基礎的類,他們分別是 Document, Field, IndexWriter, Analyzer, Directory。下面我們分別介紹一下這五個類的用途:

 

Document

Document 是用來描述文檔的,這裏的文檔可以指一個 HTML 頁面,一封電子郵件,或者是一個文本文件。一個 Document 對象由多個 Field 對象組成的。可以把一個 Document 對象想象成數據庫中的一個記錄,而每個 Field 對象就是記錄的一個字段。

接口名

備註

add(Field field)

添加一個字段(Field)到Document中

String get(String name)

從文檔中獲得一個字段對應的文本

Field getField(String name)

由字段名獲得字段值

Field[] getFields(String name)

由字段名獲得字段值的集

 

Field

Field 對象是用來描述一個文檔的某個屬性的,比如一封電子郵件的標題和內容可以用兩個 Field 對象分別描述。

即上文所說的“字段”,它是Document的片段section。

Field的構造函數:

Field(String name, String string, boolean store, boolean index, boolean token)。

Indexed:如果字段是Indexed的,表示這個字段是可檢索的。

Stored:如果字段是Stored的,表示這個字段的值可以從檢索結果中得到。

Tokenized:如果一個字段是Tokenized的,表示它是有經過Analyzer轉變後成爲一個tokens序列,在這個轉變過程tokenization中,Analyzer提取出需要進行索引的文本,而剔除一些冗餘的詞句(例如:a,the,they等,詳見org.apache.lucene.analysis.StopAnalyzer.ENGLISH_STOP_WORDS和org.apache.lucene.analysis.standard.StandardAnalyzer(String[] stopWords)的API)。Token是索引時候的基本單元,代表一個被索引的詞,例如一個英文單詞,或者一個漢字。因此,所有包含中文的文本都必須是Tokenized的。

 

Analyzer

在一個文檔被索引之前,首先需要對文檔內容進行分詞處理,這部分工作就是由 Analyzer 來做的。Analyzer 類是一個抽象類,它有多個實現。針對不同的語言和應用需要選擇適合的 Analyzer。Analyzer 把分詞後的內容交給 IndexWriter 來建立索引。

接口名

備註

addDocument(Document doc)

索引添加一個文檔

addIndexes(Directory[] dirs)

將目錄中已存在索引添加到這個索引

addIndexes(IndexReader[] readers)

將提供的索引添加到這個索引

optimize()

合併索引並優化

close()

關閉

IndexWriter

IndexWriter 是 Lucene 用來創建索引的一個核心的類,他的作用是把一個個的 Document 對象加到索引中來。

 

Directory

這個類代表了 Lucene 的索引的存儲的位置,這是一個抽象類,它目前有兩個實現,第一個是 FSDirectory,它表示一個存儲在文件系統中的索引的位置。第二個是 RAMDirectory,它表示一個存儲在內存當中的索引的位置。

熟悉了建立索引所需要的這些類後,我們就開始對某個目錄下面的文本文件建立索引了,給出了對某個目錄下的文本文件建立索引的源代碼。

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:\\index");
 
        // indexDir is the directory that hosts Lucene's index files
        File indexDir = new File("C:\\luceneIndex");
        Analyzer luceneAnalyzer = new StandardAnalyzer(Version.LUCENE_30);
        IndexWriter indexWriter = new IndexWriter(FSDirectory.open(indexDir), luceneAnalyzer, true, IndexWriter.MaxFieldLength.LIMITED);
        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(new Field("content", textReader));
                document.add(new Field("path", textFiles[i].getPath(), Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
                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());
    }
}

 

我們注意到類 IndexWriter 的構造函數需要三個參數,第一個參數指定了所創建的索引要存放的位置,他可以是一個 File 對象,也可以是一個 FSDirectory 對象或者 RAMDirectory 對象。第二個參數指定了 Analyzer 類的一個實現,也就是指定這個索引是用哪個分詞器對文擋內容進行分詞。第三個參數是一個布爾型的變量,如果爲 true 的話就代表創建一個新的索引,爲 false 的話就代表在原來索引的基礎上進行操作。接着程序遍歷了目錄下面的所有文本文檔,併爲每一個文本文檔創建了一個 Document 對象。然後把文本文檔的兩個屬性:路徑和內容加入到了兩個 Field 對象中,接着在把這兩個 Field 對象加入到 Document 對象中,最後把這個文檔用 IndexWriter 類的 add 方法加入到索引中去。這樣我們便完成了索引的創建。接下來我們進入在建立好的索引上進行搜索的部分。

 

搜索文檔

 

Query

這是一個抽象類,他有多個實現,比如TermQuery, BooleanQuery, PrefixQuery. 這個類的目的是把用戶輸入的查詢字符串封裝成Lucene能夠識別的Query。

Term

Term是搜索的基本單位,一個Term對象有兩個String類型的域組成。生成一個Term對象可以有如下一條語句來完成:Term term = new Term(“fieldName”,”queryWord”); 其中第一個參數代表了要在文檔的哪一個Field上進行查找,第二個參數代表了要查詢的關鍵詞。

TermQuery

TermQuery是抽象類Query的一個子類,它同時也是Lucene支持的最爲基本的一個查詢類。生成一個TermQuery對象由如下語句完成: TermQuery termQuery = new TermQuery(new Term(“fieldName”,”queryWord”)); 它的構造函數只接受一個參數,那就是一個Term對象。

IndexSearcher

IndexSearcher是用來在建立好的索引上進行搜索的。它只能以只讀的方式打開一個索引,所以可以有多個IndexSearcher的實例在一個索引上進行操作。

Hits

Hits是用來保存搜索的結果的。

 

介紹完這些搜索所必須的類之後,我們就開始在之前所建立的索引上進行搜索了,清單2給出了完成搜索功能所需要的代碼。

如何添加一個文檔到索引文

Document document = new Document();

document.add(new Field("content",textReader));

document.add(new Field("path",textFiles[i].getPath(), Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));

indexWriter.addDocument(document);

//最後不要忘記了關閉

indexWriter.close();

首先第一行創建了類 Document 的一個實例,它由一個或者多個的域(Field)組成。你可以把這個類想象成代表了一個實際的文檔,比如一個 HTML 頁面,一個 PDF 文檔,或者一個文本文件。而類 Document 中的域一般就是實際文檔的一些屬性。比如對於一個 HTML 頁面,它的域可能包括標題,內容,URL 等。我們可以用不同類型的 Field 來控制文檔的哪些內容應該索引,哪些內容應該存儲。如果想獲取更多的關於 Lucene 的域的信息,可以參考 Lucene 的幫助文檔。代碼的第二行和第三行爲文檔添加了兩個域,每個域包含兩個屬性,分別是域的名字和域的內容。在我們的例子中兩個域的名字分別是"content"和"path"。分別存儲了我們需要索引的文本文件的內容和路徑。最後一行把準備好的文檔添加到了索引當中。

 

從索引中刪除文檔

類IndexReader負責從一個已經存在的索引中刪除文檔。

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();

第二行用靜態方法 IndexReader.open(indexDir) 初始化了類 IndexReader 的一個實例,這個方法的參數指定了索引的存儲路徑。類 IndexReader 提供了兩種方法去刪除一個文檔,如程序中的第三行和第四行所示。第三行利用文檔的編號來刪除文檔。每個文檔都有一個系統自動生成的編號。第四行刪除了路徑爲"C:\\file_to_index\lucene.txt"的文檔。你可以通過指定文件路徑來方便的刪除一個文檔。值得注意的是雖然利用上述代碼刪除文檔使得該文檔不能被檢索到,但是並沒有物理上刪除該文檔。Lucene 只是通過一個後綴名爲 .delete 的文件來標記哪些文檔已經被刪除。既然沒有物理上刪除,我們可以方便的把這些標記爲刪除的文檔恢復過來,如清單 3 所示,首先打開一個索引,然後調用方法 ir.undeleteAll() 來完成恢復工作。

 

恢復已刪除文檔

File indexDir = new File("C:\\luceneIndex");

IndexReader ir = IndexReader.open(indexDir);

ir.undeleteAll();

ir.close();

 

如何物理上刪除文檔

File indexDir = new File("C:\\luceneIndex");

Analyzer luceneAnalyzer = new StandardAnalyzer();

IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,false);

indexWriter.optimize();

indexWriter.close();

第三行創建了類 IndexWriter 的一個實例,並且打開了一個已經存在的索引。第 4 行對索引進行清理,清理過程中將把所有標記爲刪除的文檔物理刪除。

 

提高索引性能

利用 Lucene,在創建索引的工程中你可以充分利用機器的硬件資源來提高索引的效率。當你需要索引大量的文件時,你會注意到索引過程的瓶頸是在往磁盤上寫索引文件的過程中。爲了解決這個問題, Lucene 在內存中持有一塊緩衝區。但我們如何控制 Lucene 的緩衝區呢?幸運的是,Lucene 的類 IndexWriter 提供了三個參數用來調整緩衝區的大小以及往磁盤上寫索引文件的頻率。

1.合併因子(mergeFactor)

這個參數決定了在 Lucene 的一個索引塊中可以存放多少文檔以及把磁盤上的索引塊合併成一個大的索引塊的頻率。比如,如果合併因子的值是 10,那麼當內存中的文檔數達到 10 的時候所有的文檔都必須寫到磁盤上的一個新的索引塊中。並且,如果磁盤上的索引塊的隔數達到 10 的話,這 10 個索引塊會被合併成一個新的索引塊。這個參數的默認值是 10,如果需要索引的文檔數非常多的話這個值將是非常不合適的。對批處理的索引來講,爲這個參數賦一個比較大的值會得到比較好的索引效果。

2.最小合併文檔數

這個參數也會影響索引的性能。它決定了內存中的文檔數至少達到多少才能將它們寫回磁盤。這個參數的默認值是10,如果你有足夠的內存,那麼將這個值儘量設的比較大一些將會顯著的提高索引性能。

3.最大合併文檔數

這個參數決定了一個索引塊中的最大的文檔數。它的默認值是 Integer.MAX_VALUE,將這個參數設置爲比較大的值可以提高索引效率和檢索速度,由於該參數的默認值是整型的最大值,所以我們一般不需要改動這個參數。

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;

下面我們來看一下這三個參數取不同的值對索引時間的影響,注意參數值的不同和索引之間的關係。我們爲這個實驗準備了 10000 個測試文檔。表 1 顯示了測試結果。

1:測試結果
clip_image001

通過表 1,你可以清楚地看到三個參數對索引時間的影響。在實踐中,你會經常的改變合併因子和最小合併文檔數的值來提高索引性能。只要你有足夠大的內存,你可以爲合併因子和最小合併文檔數這兩個參數賦儘量大的值以提高索引效率,另外我們一般無需更改最大合併文檔數這個參數的值,因爲系統已經默認將它設置成了最大。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章