在使用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,把它放置到源文件根目錄下面,系統會自動加載進來。
/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()
閱讀更多 java執行官 的文章