Lucene 分詞原理

Lucene是一個高性能的java全文檢索工具包,它使用的是倒排文件索引結構。該結構及相應的生成算法如下:

0)設有兩篇文章1和2
文章1的內容爲:Tom lives in Guangzhou,I live in Guangzhou too.
文章2的內容爲:He once lived in Shanghai.

1)由於lucene是基於關鍵詞索引和查詢的,首先我們要取得這兩篇文章的關鍵詞,通常我們需要如下處理措施
a.我們現在有的是文章內容,即一個字符串,我們先要找出字符串中的所有單詞,即分詞。英文單詞由於用空格分隔,比較好處理。中文單詞間是連在一起的需要特殊的分詞處理。
b.文章中的”in”, “once” “too”等詞沒有什麼實際意義,中文中的“的”“是”等字通常也無具體含義,這些不代表概念的詞可以過濾掉
c.用戶通常希望查“He”時能把含“he”,“HE”的文章也找出來,所以所有單詞需要統一大小寫。
d.用戶通常希望查“live”時能把含“lives”,“lived”的文章也找出來,所以需要把“lives”,“lived”還原成“live”
e.文章中的標點符號通常不表示某種概念,也可以過濾掉
在lucene中以上措施由Analyzer類完成

經過上面處理後
文章1的所有關鍵詞爲:[tom] [live] [guangzhou] [live] [guangzhou]
文章2的所有關鍵詞爲:[he] [live] [shanghai]

2) 有了關鍵詞後,我們就可以建立倒排索引了。上面的對應關係是:“文章號”對“文章中所有關鍵詞”。倒排索引把這個關係倒過來,變成:“關鍵詞”對“擁有該關鍵詞的所有文章號”。文章1,2經過倒排後變成
關鍵詞 文章號
guangzhou 1
he 2
i 1
live 1,2
shanghai 2
tom 1

通常僅知道關鍵詞在哪些文章中出現還不夠,我們還需要知道關鍵詞在文章中出現次數和出現的位置,通常有兩種位置:a)字符位置,即記錄該詞是文章中第幾個字符(優點是關鍵詞亮顯時定位快);b)關鍵詞位置,即記錄該詞是文章中第幾個關鍵詞(優點是節約索引空間、詞組(phase)查詢快),lucene中記錄的就是這種位置。

加上“出現頻率”和“出現位置”信息後,我們的索引結構變爲:
關鍵詞 文章號[出現頻率] 出現位置
guangzhou 1[2] 3,6
he 2[1] 1
i 1[1] 4
live 1[2],2[1] 2,5,2
shanghai 2[1] 3
tom 1[1] 1

以live 這行爲例我們說明一下該結構:live在文章1中出現了2次,文章2中出現了一次,它的出現位置爲“2,5,2”這表示什麼呢?我們需要結合文章號和出現頻率來分析,文章1中出現了2次,那麼“2,5”就表示live在文章1中出現的兩個位置,文章2中出現了一次,剩下的“2”就表示live是文章2中第 2個關鍵字。

以上就是lucene索引結構中最核心的部分。我們注意到關鍵字是按字符順序排列的(lucene沒有使用B樹結構),因此lucene可以用二元搜索算法快速定位關鍵詞。

實現時 lucene將上面三列分別作爲詞典文件(Term Dictionary)、頻率文件(frequencies)、位置文件 (positions)保存。其中詞典文件不僅保存有每個關鍵詞,還保留了指向頻率文件和位置文件的指針,通過指針可以找到該關鍵字的頻率信息和位置信息。

Lucene中使用了field的概念,用於表達信息所在位置(如標題中,文章中,url中),在建索引中,該field信息也記錄在詞典文件中,每個關鍵詞都有一個field信息(因爲每個關鍵字一定屬於一個或多個field)。

爲了減小索引文件的大小,Lucene對索引還使用了壓縮技術。首先,對詞典文件中的關鍵詞進行了壓縮,關鍵詞壓縮爲<前綴長度,後綴>,例如:當前詞爲“阿拉伯語”,上一個詞爲“阿拉伯”,那麼“阿拉伯語”壓縮爲<3,語>。其次大量用到的是對數字的壓縮,數字只保存與上一個值的差值(這樣可以減小數字的長度,進而減少保存該數字需要的字節數)。例如當前文章號是16389(不壓縮要用3個字節保存),上一文章號是16382,壓縮後保存7(只用一個字節)。

下面我們可以通過對該索引的查詢來解釋一下爲什麼要建立索引。
假設要查詢單詞 “live”,lucene先對詞典二元查找、找到該詞,通過指向頻率文件的指針讀出所有文章號,然後返回結果。詞典通常非常小,因而,整個過程的時間是毫秒級的。
而用普通的順序匹配算法,不建索引,而是對所有文章的內容進行字符串匹配,這個過程將會相當緩慢,當文章數目很大時,時間往往是無法忍受的。

 

 

 

lucene提供的demo程序中只支持英文的索引,下文將介紹如何在demo中添加中文索引

1.下載lucene的源碼和中文解析器源碼
其中中文解析器的下載地址是:http://svn.apache.org/repos/asf/lucene/java/trunk/contrib/

2.重新打包lucene-1.4.3.jar使其包含中文解析器

修改demo程序中語言解析器的調用:
...
try {
      IndexWriter writer = new IndexWriter("index", new StandardAnalyzer(),
true);
      indexDocs(writer, new File(args[0]));
...

改爲
...
try {
      IndexWriter writer = new IndexWriter("index", new ChineseAnalyzer(),
true);
      indexDocs(writer, new File(args[0]));
...

重新打包lucene-demos-1.4.3.jar

3.按照lucene幫助建立索引,之後我們就可以進行中文檢索了

由於lucene提供的中文解析器沒有配合字典使用,並且採用的是多元分詞,效率可能會比較低,
但是爲二次開發提供了比較好的基礎。

 

 

Lucene的索引接口


 在學習索引的時候,首先需要熟悉幾個接口:


4.1.1分析器Analyzer


        分析器主要工作是篩選,一段文檔進來以後,經過它,出去的時候只剩下那些有用的部分,其他則剔除。而這個分析器也可以自己根據需要而編寫。
        org.apache.lucene.analysis.Analyzer:這是一個虛構類,以下兩個藉口均繼承它而來。


        org.apache.lucene.analysis.SimpleAnalyzer:分析器,支持最簡單拉丁語言。


        org.apache.lucene.analysis.standard.StandardAnalyzer:標準分析器,除了拉丁語言還支持亞洲語言,並在一些匹配功能上進行完善。在這個接口中還有一個很重要的構造函數:StandardAnalyzer(String[] stopWords),可以對分析器定義一些使用詞語,這不僅可以免除檢索一些無用信息,而且還可以在檢索中定義禁止的政治性、非法性的檢索關鍵詞。


4.1.2 IndexWriter


        IndexWriter的構造函數有三種接口,針對目錄Directory、文件File、文件路徑String三種情況。
例如IndexWriter(String path, Analyzer a, boolean create),path爲文件路徑,a爲分析器,create標誌是否重建索引(true:建立或者覆蓋已存在的索引,false:擴展已存在的索引。)
       一些重要的方法:

 

接口名

備註

addDocument(Document doc)

索引添加一個文檔

addIndexes(Directory[] dirs)

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

addIndexes(IndexReader[] readers)

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

optimize()

合併索引並優化

close()

關閉

 


       IndexWriter爲了減少大量的io維護操作,在每得到一定量的索引後建立新的小索引文件(筆者測試索引批量的最小單位爲10),然後再定期將它們整合到一個索引文件中,因此在索引結束時必須進行wirter. optimize(),以便將所有索引合併優化。


4.1.3 org.apache.lucene.document


 以下介紹兩種主要的類:
 a)org.apache.lucene.document.Document:
        Document文檔類似數據庫中的一條記錄,可以由好幾個字段(Field)組成,並且字段可以套用不同的類型(詳細見b)。Document的幾種接口:
 

 

接口名

備註

add(Field field)

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

String get(String name)

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

Field getField(String name)

由字段名獲得字段值

Field[] getFields(String name)

由字段名獲得字段值的集

 


 b)org.apache.lucene.document.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的。
     Field的幾種接口:

 

Name

Stored

Indexed

Tokenized

use

Keyword(String name,

        String value)

Y

Y

N

date,url

Text(String name, Reader value)

N

Y

Y

short text fields:

title,subject

Text(String name, String value)

Y

Y

Y

longer text fields,

like “body”

UnIndexed(String name,

String value)

Y

N

N

 

UnStored(String name,

         String value)

N

Y

Y

 

?

 


5. 利用Lucene進行檢索


5.1 一段簡單的檢索代碼

 

    //需要捕捉IOException,ParseException異常
    //處理檢索條件
    Query query = QueryParser.parse("入門", "text", analyzer);

    //檢索
    Searcher searcher = new IndexSearcher("./index");//"index"指定索引文件位置
Hits hits = searcher.search(query);

    //打印結果值集
    for (int i = 0; i < hits.length(); i++) {
      doc = hits.doc(i);
      String id = doc.get("id");
      System.out.println("found " + "入門" + " on the id:" + id);
}

 

5.2 利用Lucene的檢索接口


5.2.1 Query與QueryParser


        主要使用方法:
QueryParser .parse(String query, String field, Analyzer analyzer),例如:
Query query = QueryParser.parse("入門", "text", analyzer);
"入門"爲檢索詞, "text"爲檢索的字段名, analyzer爲分析器


5.2.2 Hits與Searcher


       Hits的主要使用接口:
 

 

接口名

備註

Doc(int n)

返回第n個的文檔的所有字段

length()

返回這個集中的可用個數

 

 


6. Lucene的其他使用


6.1 Lucene 的索引修改


        下面給出一段修改索引的代碼,請根據Lucene的API解讀:


 
  public void addIndex(String idStr, String valueStr) {
    StandardAnalyzer analyzer = new StandardAnalyzer();
    IndexWriter writer = null;
    try {
      writer = new IndexWriter(indexPath, analyzer, false);
      writer.mergeFactor = 2; //修正lucene 1.4.2 bug,否則不能準確反映修改

          Document doc = new Document();
          doc.add(Field.UnIndexed("id", idStr));//“id”爲字段名,“1”爲字段值
          doc.add(Field.Text("text", valueStr));
      writer.addDocument(doc);

      writer.optimize();
      writer.close();
    }
    catch (IOException ioe) {
      ioe.printStackTrace();
    }
  }

 
  public void deleteIndex(String idStr) {
    try {
      Directory dirt = FSDirectory.getDirectory(indexPath, false);
      IndexReader reader = IndexReader.open(dirt);
      IndexXML.deleteIndex(idStr, reader);
      reader.close();
      dirt.close();
    }
    catch (IOException ioe) {
      ioe.printStackTrace();
    }
  }


6.2 Lucene 的檢索結果排序


        Lucene的排序主要是對org.apache.lucene.search.Sort的使用。Sort可以直接根據字段Field生成,也可以根據標準的SortField生成,但是作爲Sort的字段,必須符合以下的條件:唯一值以及Indexed。可以對Integers, Floats, Strings三種類型排序。
        對整數型的ID檢索結果排序只要進行以下的簡單操作:

 Sort sort = new Sort("id");
Hits hits = searcher.search(query, sort);

       用戶還可以根據自己定義更加複雜的排序,詳細請參考API。


7 總結


        Lucene給java的全文索引檢索帶來了非常強大的力量,以上僅對Lucene進行簡單的入門說明。

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