Lucene學習總結

在使用Lucene前,我們先大致熟悉下Lucene的幾個核心類。

核心索引類:

  • public class IndexWriter

索引過程的中心組件,把它想象成一個可以對索引進行寫操作的對象。

  • public abstract class Directry

Directory代表索引所在的位置,該抽象類有兩個具體的子類實現。FSDirectory表示存儲在文件系統的索引位置,RAMDirectory表示存儲在內存中的索引位置。

  • public abstract class Analyzer
  • 分詞組件。在建立索引前首先要對文檔進行分詞,Lucene默認有一些分詞類的實現,自己實現的分詞要繼承該類。
  • public final class Document
  • Document類似於數據庫中的一條記錄,它由好幾個字段Field組成。
  • public final class Field
  • Field用來描述文檔的某個屬性,例如文章的標題,內容等等。

核心搜索類:

  • public class IndexSeacher
  • 用來在已經建好的索引上進行搜索操作
  • public final class Term
  • 搜索的基本單元。Term對象有兩個域組成。Term term = new Term("fieldName","queryWord");
  • public abstract class Query
  • 抽象類,有很多具體實現類。該類主要作用把用戶輸入的查詢語句轉換為Lucene能夠是別的query。
  • public final class Hits(TopDocs)
  • Hits是用來保存查詢得到的結果的。最新版的Lucene中,TopDocs已代替了Hits。

我們拿一張紙、一支筆,填寫下面的表格:

序號

文件名

文件路徑

文件類型

文件大小

修改時間

內容

……

填完以後,搜索的時候就可以照著這張紙“按圖索驥”了。

在lucene中,這張紙叫做Directory(也就是索引保存的目錄),這支筆叫做IndexWriter,表格中一條記錄叫做Document,記錄中的每項叫做Field。

下面我們來看第一個簡單的Lucene實現索引的例子(Lucene版本為4.10.1)。

public class LuceneDemo {

public static void main(String[] args){

//RAMDirectory(內存路徑)繼承自Directory抽象類,另一個繼承自該類的是FSDirectory(文件系統路徑),Directory dir = FSDirectory.open(new File("此處寫索引存儲的位置,"));

Directory dir = new RAMDirectory();

//SimpleAnalyzer繼承自抽象類Analyzer,是分詞組件,不同語言有不同的分詞組件包,也可以自己定義實現該抽象類

Analyzer analyzer = new SimpleAnalyzer();

//定義IndexWriterConfig

IndexWriterConfig iwc = new IndexWriterConfig(Version.LATEST, analyzer);

//定義document對象

Document doc = new Document();

try {

//第一步,切詞入庫,創建索引。定義IndexWriter對索引進行“寫”操作

IndexWriter iw = new IndexWriter(dir, iwc);

//Field對象的構造方法有四個參數,前兩個參數表示要建立索引的name和value,name指索引的名稱,value指要建立索引的“文檔對象”,例如博客的標題、正文

//Field.Store有YES和NO兩個值,表示是否存儲該Field

//Field.Index有5個不同的取值,ANALYZED,ANALYZED_NO_NORMS,NOT_ANALYZED,NOT_ANALYZED_NO_NORMS,NO,根據不同情況選擇是否分詞

doc.add(new Field("title", "james bonde", Field.Store.YES, Field.Index.ANALYZED));

doc.add(new Field("content","He want to go to school next year.",Field.Store.YES,Field.Index.ANALYZED));

doc.add(new Field("doc","He will go to his mother's home.",Field.Store.YES,Field.Index.ANALYZED));

iw.addDocument(doc);

iw.close();

//第二步,查詢索引,返回結果

IndexReader ir = DirectoryReader.open(dir);

//定義IndexSearcher

IndexSearcher is = new IndexSearcher(ir);

//定義Term,new Term("doc", "home"),第一個值表示要搜索的域,第二個則表示搜索值

Term term = new Term("doc", "home");

//TermQuery繼承自Query抽象類,是Lucene最基本的查詢

Query query = new TermQuery(term);

//執行查詢,返回TopDocs對象結果集

TopDocs td = is.search(query, 10);

for(int i=0;i

Document d = is.doc(td.scoreDocs[i].doc);

System.out.println("----------"+d.getField("title"));

System.out.println("----------"+d.getField("content"));

System.out.println("----------"+d.getField("doc"));

}

dir.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

索引的創建、修改和刪除

首先,我們來看一個例子:開源中國社區每天都有人發佈新的博客,同時也有很多人在進行修改和刪除博客的操作。如果我們只更新博客數據而不更新對應的索引數據,這會帶來那些問題呢?

新增的博客信息不能夠及時被用戶搜索到;

修改的博客信息查詢時依然顯示之前的內容;

刪除的博客信息查詢時存在但實際已被刪除。

因此,為了提高系統搜索的準確性和實時性,我們在進行數據更新的同時,也會更新與之對應的索引數據,這樣業務數據就可以保持與索引數據的一致,上面的幾個問題也就隨之解決了。

首先,我們來看新增索引的操作,這個比較簡單,之前的例子裡面已經有講到:

//當新增博客時,索引也增量更新

public void addLuceneIndex(Blog blog){

try {

IndexWriter writer = new IndexWriter(directory, config);

Document doc = new Document();

//文章id,需要存儲,查詢結果的鏈接需要,但不需要檢索

doc.add(new Field("id",blog.getString("id"),Field.Store.YES,Field.Index.NO));

//文章標題,需要存儲也需要切詞索引

doc.add(new Field("title",blog.getString("title"),Field.Store.YES,Field.Index.ANALYZED));

//文章內容一般會比較長,所以不需要存儲,但需要切詞索引

doc.add(new Field("content",blog.getString("content"),Field.Store.NO,Field.Index.ANALYZED));

//文章作者,需要存儲,整體索引但不切詞

doc.add(new Field("author",blog.getString("author"),Field.Store.YES,Field.Index.NOT_ANALYZED));

writer.addDocument(doc);

writer.forceMerge(1);

writer.commit();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

當博客被修改時,對應索引也執行更新操作,實際後臺代碼執行的是先刪除再新增操作。

//索引更新操作

public void updateLuceneIndex(Blog blog){

try {

IndexWriter writer = new IndexWriter(directory, config);

Document doc = new Document();

writer.updateDocument(new Term("id", blog.getString("id")), doc);

writer.forceMerge(1);

writer.commit();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

當文章刪除時,對應索引也執行刪除操作

//索引刪除操作

public void delLuceneIndex(Blog blog){

try {

indexWriter.deleteDocuments(new Term("id", blog.getString("id"))); // Document刪除

} catch (IOException e) {

e.printStackTrace();

}

}

最後說明一下,索引文件的增、刪、改在實際應用過程中也是有很多策略的。比如對於搜索實時性要求比較高的系統,可以採取實時更新的策略,在更新記錄時同時更新索引;如果系統對搜索的實時性要求不高,且服務器資源有限,可以設置一個定時任務,把白天更新的記錄都標記出來,在凌晨服務器空閒的時候批量更新。總之,可以根據自己的需要去靈活的應用。

分詞(切詞)

分詞也叫作切詞,是指把文檔的內容按照一定的規則切分成一個個獨立的詞語,通俗的說就是把句子切分成詞語。分詞是影響Lucene查詢效率和查詢準確率的關鍵因素。所有的分詞器都繼承自Lucene的Analyzer,今天介紹最流行和通用的中文分詞器IKAnalyzer的使用。

Lucene默認實現的有英文分詞。英文分詞相對簡單,主要是對每個單詞的單複數,時態等做轉換即可。而中文分詞相對更復雜一些。因為中文的詞庫本身就非常龐雜,同一個句子可能有好幾種分詞法,不同的分詞法可能就會導致不同的查詢結果。IKAnalyzer為我們解決以上問題提供了很好的方案,它允許我們可以個性化定義擴展詞庫,而且分詞效率極高。

下面我們來看下IKAnalyzer的配置文件IKAnalyzer.cfg.xml,把它放置到源文件根目錄下面,系統會自動加載進來。

IKAnalyzer擴展配置

/com/jfinal/lucene/ext.dic;

/com/jfinal/lucene/ft_main2012.dic;

/com/jfinal/lucene/ft_quantifier.dic;

/com/jfinal/lucene/stop.dic

ext.dic用來定義自己的擴展詞庫。比如特定的地名,人名,就相當於告訴分詞器如果遇到這些詞彙就把它們做單獨分詞;

stop.dic用來定義自己的擴展停止詞字典,停止詞就是指那些最普通的,沒有特定含義的詞。比如英語裡面的a ,the,漢語裡面的了,又等等。

把IKAnalyzer的jar包拷貝到lib下,使用時新建對象即可。

Analyzer analyzer = new IKAnalyzer()


分享到:


相關文章: