Lucene
的學習
??????
通過這幾天的看書和學習,對
Lucene
有了更進一步的認識,所以總結一下這些天的學習成果把
Lucene
的學習心得也學出來。
1?????????
Lucene
的認識
提到
Lucene
很多人都知道這個開源的搜索工具,其魅力也是很大的。它讓我們對搜索引擎的認識不在那么神秘,也不會在覺得百度和
google
的技術多么的高深沒測,其實其原理都是一樣的,只是他們要做的更好,走的更遠罷了。
Lucene
可以對任何的數據做索引和搜索,說這樣的話其實不過分,真的就是這樣,只要你能處理好這些數據,交給
Lucene
去建立索引它都可以幫你把這些數據給檢索出來,是不是很好玩了。真正好玩的地方還在后面呢。
2?????????
Lucene
的學習
前面已經對
Lucene
有了一些了解,現在我們想象它怎么去搜索這些數據呢,如果知道倒排索引,你就知道了,其實
lucene
檢索的是它自己建立的索引,從索引中的到數據的指針,從而得到數據。其實就這么簡單。
提到索引,現在的索引技術中有:倒排索引、后綴數組和簽名文件這三種,其中后綴數組這種技術雖然檢索速度也很快,但是它的數據結構構造和維護都是相當麻煩的所以不可取了。我也懶得去看了。至于簽名文件嘛,那是
80
年代的玩意了,現在已經過時了?,F在可是倒排索引的天下?。∠嘈虐俣群?/span>
google
都是這種技術。
3?????????
索引的建立
?
我們從索引的建立入手:
我們建立一個
lucene
的索引時必須先建立該索引文件存放的位置,看一下代碼:
IndexWriter writer = null;
writer = new IndexWriter("c:\\index", new CJKAnalyzer(), true);
這段代碼就時建立一個索引前所必須的操作,先聲明這個
IndexWriter
,實例化它你必須傳入三個參數。他們分別代表:你要建立索引文件的存放位置、你要使用索引建立的分詞方法、是否重新建立索引。這樣你就告訴
lucene
我要在
c
盤的
index
目錄下建立索引文件,我要使用車東老師的二分詞算法做分析器、我要在這個目錄下刪除以前的索引或任何文件創立我的索引文件。
索引的建立有三種方式,讓我一一道來:
1
、
new IndexWriter(new RAMDirectory(), new StandardAnalyzer(), true);
在內存中建立索引,速度最快但是耗資源,而且重啟就沒了。
2
、
new IndexWriter(FSDirectory.getDirectory(path, true), new StandardAnalyzer(), true);
在文件系統中建立索引,這里有兩個參數,分別是:建立索引的路徑、是否要刪除當前目錄下的文件重新建立索引。
3
、
new IndexWriter("c:\\index", new CJKAnalyzer(), true);
最常見的一種,在制定目錄下建立索引,看了源碼你就知道這種方法也是用的第二種方式。
Lucene
的源碼:
public IndexWriter(String path, Analyzer a, boolean create)
?????? throws IOException {
?this(FSDirectory.getDirectory(path, create), a, create, true);
? }
我想的沒錯。
Indexwriter
性能調整參數:
第一個優化的參數
:
mergeFactor
這個參數用于控制
lucene
在把索引從內存寫入到磁盤上的文件系統時內存最大的
Document
對象的數量。這個數要根據你的計算機設置,默認情況下是
10
。
???
第二個優化的參數
:
maxMergeFactor
這個參數用來設置當有多少個
Segment
時進行合并操作。當然我們知道當索引文件太多的話其檢索的速度就會很慢,所以我們要當文件數量一定時讓它進行索引的合并。這樣就可以加快索引速度,但是這個值要根據你的情況而定。當文檔數量較多時我們將值設大些,當文檔數量較少時我們將值設小些。
第三個優化的參數
:
minMergeDocs
這個參數用于控制內存中文檔的數量。
?
這樣我們建立索引已經完成,接下來我們要建立
Document
對象,因為你必須告訴我要搜索什么吧!好了,看看源碼:
File file = new File("1.txt");
Document doc = new Document();
doc.add(Field.UnIndexed("filename", file.getName()));
FileInputStream fis = new FileInputStream(file);
byte[] b = new byte[fis.available()];
fis.read(b);
String content = new String(b);
doc.add(Field.Text("content", content));
fis.close();
以上我們就完成了將
1.txt
文件放到我們的
Document
對象了。這里我們用了
Field.Text();
這樣的操作和
doc.add();
這樣的方法建立的。這也是建立索引的必須。
稍微介紹一下
Field
,它就是你要建立索引的字段。它分別有
類型
/
方法
|
是否分詞
|
是否索引
|
是否存儲
|
常用實例
|
Keyword(String,String)
Keyword(String,Date)
|
否
|
是
|
是
|
電話號碼,身份證,人名,地名,日期
|
Unindexed(String,String)
|
否
|
否
|
是
|
文檔類型,文檔名稱
|
UnStored(String,String)
|
是
|
是
|
否
|
文檔的標題和內容
|
Text(String,String)
|
是
|
是
|
是
|
文檔的標題和內容
|
Text(String,Reader)
|
是
|
是
|
否
|
文檔的標題和內容
|
???
這樣我們要建什么樣的索引就對號入座吧,只要最后我們使用
doc.add(Field.Text("content", content));
把它添加到
Document
中就可以了。
???
這時我們的文檔已經建立好了,現在就開始向索引中添加文檔吧!這里我們使用
writer.addDocument(doc);
來向
Indexwriter
索引中添加構造好的文檔。
這樣我們是不是就可以說我們已經建立完了索引呢,其實不然,我們還要優化優化,這樣才快嘛!對不對?
??? writer.optimize();
這樣一句話就可以實現索引優化了,具體的優化過程我就不說了,是不是很簡單。但是一定不要忘了哦。調用這個方法時最好建立一個合適的周期。定期進行優化。
???
好了,這樣我們就完成了索引的建立了。
???
下面我們看看縮影的合并吧!
當我們在很多地方建立了很多的索引后,想要合并這些索引我們怎么辦呢?
???
使用
IndexWriter.assIndexs(New Directory[]{path});
就可以對
path
路徑下的索引合并到當前的索引中了。
???
下面再看看索引的刪除吧!
???
有一些過時的索引我們需要刪除,怎么辦呢?
IndexReader reader = IndexReader.open("c:\\index");
??? reader.delete(0);
這樣我們就可以按照文檔的順序刪除對應的文檔了,但是這樣不太現實,不對嗎?我們怎么會知道文檔的順序呢?
下面我們看看第二中方法:
IndexReader reader = IndexReader.open("c:\\index");
reader.delete(new Term("name","word1"));
reader.close();
按照字段來刪除對應的文檔,這樣合理多了。以后要刪除時就按照詞條的方式去刪除吧
!
索引鎖:
write.lock , commit.lock.
write.lock
是為了避免幾個線程同時修改一個索引文檔而設置。當實例一個
indexwrite
時建立和使用
indexReader
刪除文檔時建立。
Commit.lock
該鎖主要在
segment
在建立,合并或讀取時生成。
4?????????
Lucene
的搜索
?
以上完成了索引的建立和一些關于索引的知識,但是光有索引是不行的,我們真正要做的檢索,這才是我們的關鍵?,F在我們看看
lucene
的檢索吧。
認識檢索從檢索的工具開始吧!
IndexSearcher
類是
lucene
用于檢索的工具類,我們在檢索之前要得到這個類的實例。
第一步我們看以下代碼:
IndexSearcher searcher = new IndexSearcher("c:\\index");
創建
IndexSearcher
實例需要告訴
lucene
索引的位置,就是你
IndexWrite
的文件路徑。
Query query = null;
Hits hits = null;
query = QueryParser.parse(key1, "name", new StandardAnalyzer());
hits = searcher.search(query);
if (hits != null) {
?????????? if (hits.length() == 0) {
????????????? System.out.println("
沒有找到任何結果
");
?????????? } else {
????????????? System.out.print("
找到
");
????????????? for (int i = 0; i < hits.length(); i++) {
????????????????? Document d = hits.doc(i);
????????????????? String dname = d.get("title");
????????????????? System.out.print(dname + "?? " );
????????????? }
?????????? }
?????? }
}
以上就是一個完整的檢索過程,這里我們看見了個
Query
和
Hits
,這兩個類就是比較關鍵的了,我們先從檢索結果的
Hits
類說起。
我們使用
Hits
經常使用的幾個方法有:
length() :?
返回搜索結果的總數量。
Doc(int n) :
放回第
n
個文檔。
Id(int n) :
返回第
n
個文檔的內部編號。
Sorce(int n) :
返回第
n
個文檔的得分。
看見這個
Sorce(int n)
這個方法,是不是就可以聯想到搜索引擎的排序問題呢,像百度的推廣是怎么做出來的呢
,
可想而知吧,那就說明必定存在一中方法可以動態的改變某片文檔的得分。對了,
lucene
中可以使用
Document
的
setBoost
方法可以改變當前文檔的
boost
因子。
下面我們看看:
Document doc1 = new Document();
doc1.add(Field.Text("contents", "word1 word"));
doc1.add(Field.Keyword("path", "path\\document1.txt"));
?doc1.setBoost(1.0f);
?
?
這樣我們就在改變了篇文檔的評分了,當
boost
的值越大它的分值就越高,其出現的位置就越靠前。
讓我們再來看看
lucene
為我們提供的各種
Query
吧。
第一、??
按詞條搜索
-
TermQuery
query = new TermQuery(new Term("name","word1"));
hits = searcher.search(query);
這樣就可以把
field
為
name
的所有包含
word1
的文檔檢索出來了。
第二、?
“與或”搜索
-
BooleanQuery
它實際是一個組合
query
看看下面的代碼:
query1 = new TermQuery(new Term("name","word1"));
query2 = new TermQuery(new Term("name","word2"));
query = new BooleanQuery();
query.add(query1, false, false);
query.add(query2, false, false);
hits = searcher.search(query);
看看
booleanQuery
的用法吧:
true & true :
表明當前加入的字句是必須要滿足的。相當于邏輯與。
false & true :
表明當前加入的字句是不可一被滿足的,
相當于邏輯非。
false & false :
表明當前加入的字句是可選的,相當于邏輯或。
true & true :
錯誤的情況。
Lucene
可以最多支持連續
1024
的
query
的組合。
第三、?
在某一范圍內搜索
-
RangeQuery
IndexSearcher searcher = new IndexSearcher("c:\\index");
???? Term beginTime = new Term("time","200001");
???? Term endTime = new Term("time","200005");
???? Hits hits = null;
???? RangeQuery query = null;
???? query = new RangeQuery(beginTime, endTime, false);
???? hits = searcher.search(query);
RangeQuery
的構造函數的參數分別代表起始、結束、是否包括邊界。這樣我們就可以按照要求檢索了。
第四、?
使用前綴檢索
-
PrefixQuery
這個檢索的機制有點類似于
indexOf()
從前綴查找。這個常在英文中使用,中文中就很少使用了。代碼如下:
IndexSearcher searcher = new IndexSearcher("c:\\index");
?????? Term pre1 = new Term("name", "Da");
?????? query = new PrefixQuery(pre1);
?????? hits = searcher.search(query);
第五、?
多關鍵字的搜索
-
PhraseQuery
可以多個關鍵字同時查詢。使用如下:
query = new PhraseQuery();
?????? query.add(word1);
?????? query.add(word2);
?????? query.setSlop(0);
?????? hits = searcher.search(query);
?????? printResult(hits, "'david'
與
'mary'
緊緊相隔的
Document");
?????? query.setSlop(2);
?????? hits = searcher.search(query);
?????? printResult(hits, "'david'
與
'mary'
中相隔兩個詞的短語
");
???
這里我們要注意
query.setSlop();
這個方法的含義。
query.setSlop(0);?
緊緊相連
(這個的條件比較苛刻)
query.setSlop(2);?
相隔
第六、?
使用短語綴搜索
-
PharsePrefixQuery
使用
PharsePrefixQuery
可以很容易的實現相關短語的檢索功能。
實例:
query = new PhrasePrefixQuery();
?????? //
加入可能的所有不確定的詞
Term word1 = new Term("content", "david");
?????? Term word2 = new Term("content", "mary");
?????? Term word3 = new Term("content", "smith");
?????? Term word4 = new Term("content", "robert");
?????? query.add(new Term[]{word1, word2});
?????? //
加入確定的詞
?????? query.add(word4);
?????? query.setSlop(2);
?????? hits = searcher.search(query);
?????? printResult(hits, "
存在短語
'david robert'
或
'mary robert'
的文檔
");
第七、?
相近詞語的搜索
-
fuzzyQuery
可以通俗的說它是一種模糊查詢。
?
?
實例:
Term word1 = new Term("content", "david");
?????? Hits hits = null;
?????? FuzzyQuery query = null;
?????? query = new FuzzyQuery(word1);
?????? hits = searcher.search(query);
?????? printResult(hits,"
與
'david'
相似的詞
");
第八、?
使用通配符搜索
-
WildcardQuery
實例:
IndexSearcher searcher = new IndexSearcher("c:\\index");
?????? Term word1 = new Term("content", "*ever");
?????? Term word2 = new Term("content", "wh?ever");
?????? Term word3 = new Term("content", "h??ever");
?????? Term word4 = new Term("content", "ever*");
?????? WildcardQuery query = null;
?????? Hits hits = null;
?????? query = new WildcardQuery(word1);
?????? hits = searcher.search(query);
?????? printResult(hits, "*ever");
?????? query = new WildcardQuery(word2);
?????? hits = searcher.search(query);
?????? printResult(hits, "wh?ever");?????
?????? query = new WildcardQuery(word3);
?????? hits = searcher.search(query);
?????? printResult(hits, "h??ever");?????
?????? query = new WildcardQuery(word4);
?????? hits = searcher.search(query);
?????? printResult(hits, "ever*");
???
由上可以看出通配符?代便
1
個字符,
*
代表
0
到多個字符。
Lucene
現在支持以上八中的搜索方式,我們可以根據需要選擇適合自己的搜索方式。當然上面提供的一些可能對英文還是比較有效,中文就不可取了,所以我們開始想想百度,我們只在一個輸入框中搜索結果。有了這個疑問我們揭開下一章的討論吧!
查詢字符串的解析:這個就是我們經常在一個輸入框中輸入我們要檢索的文字,交給搜索引擎去幫我們分詞。
QueryParser
類就是對查詢字符串的解析類。
看看它的用法:
?
query = QueryParser.parse(key1, "name", new StandardAnalyzer());
hits = searcher.search(query);
它直接返回一個
Query
對象。需要傳入的參數分別是:
用戶需要查詢的字符串、需要檢索的對應字段名稱、采用的分詞類。
Analyzer analyzer = new CJKAnalyzer();
String[] fields = {"filename", "content"};
Query query = MultiFieldQueryParser.parse(searchword, fields, analyzer);
Hits hits = searcher.search(query);
QueryParser
的“與”
和
“或”:
QueryParser
之間默認是或,我們想改變為與的話加入以下代碼:
QueryParser.setOperator(QueryParser.DEFAULT_OPERATOR_AND);
就可以了。
5?????????
高級搜索技巧
前面我們已經介紹了一般情況下
lucene
的使用技巧,現在我們探討一下高級搜索的技巧吧!
1、
對搜索結果進行排序:
1)
使用
sort
類排序:
??? Sort sort = new Sort();
???
??? hits = searcher.search(query,sort);
這種方式是使用默認的
sort
排序方式進行排序。默認的
sort
排序是按照相關度進行排序。即通過
luence
的評分機制進行排序。
2)
對某一字段進行排序
?????? Sort sort = new Sort(
“
content
”
);
?
??? hits = searcher.search(query,sort);
3)
對多個字段進行排序
Sort sort = new Sort(new SortField[]{new SortField("title"),new SortField("contents")});
hits = searcher.search(query,sort);
2、
多域搜索和多索引搜索:
在使用
luecene
時,如果查詢的只是某些
terms
,而不關心這些詞條到時來自那個字段中時。這時可以使用
MultiFieldQueryParser
類。這個用于用戶搜索含有某個關鍵字是否存在在字段中,他們之間的關系使用
OR
連接。即不管存在在哪一個字段都會顯示顯示出來。
使用
MultiSearcher
可以滿足同時多索引的搜索需求。
Searcher[] searchers = new Searcher[2];
searchers[0] = new IndexSearcher(indexStoreB);
searchers[1] = new IndexSearcher(indexStoreA);
??????? //
創建一個多索引檢索器
Searcher mSearcher = new MultiSearcher(searchers);
3、
???
對搜索結果進行過濾:
1)???
對時間進行過濾
?
??????
通常情況下我們對搜索結果要進行過濾顯示,即只顯示過濾后的結果。
doc.add(Field.Keyword("datefield", DateField.timeToString(now - 1000)));
DateFilter df1 = DateFilter.Before("datefield", now);
2)???
???
查詢過濾器
通過查詢過濾器可以過濾一部分的信息。
Filter filter = new Filter()
???
??? {
???
?? public BitSet bits (IndexReader reader) throws IOException
???
????? {
???
??????? BitSet bitset = new BitSet(5);
???
??????? bitset.set (1);
???
??????? bitset.set (3);
???
??????? return bitset;
???
????? }
???
??? };
???
??? //
生成帶有過濾器的查詢對象
???
??? Query filteredquery = new FilteredQuery (query, filter);
?????? //
返回檢索結果
???
??? Hits hits = searcher.search (filteredquery);
?
這樣我們就可以使用自己定義的過濾方式去過濾信息了。
3)???
帶緩存的過濾器:
使用待緩存的過濾器我們可以重用過濾功能,如下:
MockFilter filter = new MockFilter();
?CachingWrapperFilter cacher = new CachingWrapperFilter(filter);
???
??? cacher.bits(reader);
以上介紹完了現在學習
luence
,沒有太詳細的介紹它的實現,因為它對于我們來說是一個工具,既然是工具我們就要會用就可以了。
posted on 2006-09-15 15:29
安文豪 閱讀(4259)
評論(11) 編輯 收藏