而盡可能減少IndexSearcher的創建和對搜索結果的前臺的緩存也是必要的。

Lucene面向全文檢索的優化在于首次索引檢索后,并不把所有的記錄(Document)具體內容讀取出來,而是只將所有結果中匹配度最高的頭
100條結果(TopDocs)的ID放到結果集緩存中并返回,這里可以比較一下數據庫檢索:如果是一個10,000條的數據庫檢索結果集,數據庫是一定
要把所有記錄內容都取得以后再開始返回給應用結果集的。所以即使檢索匹配總數很多,Lucene的結果集占用的內存空間也不會很多。對于一般的模糊檢索應
用是用不到這么多的結果的,頭100條已經可以滿足90%以上的檢索需求。

如果首批緩存結果數用完后還要讀取更后面的結果時Searcher會再次檢索并生成一個上次的搜索緩存數大1倍的緩存,并再重新向后抓取。所以如果
構造一個Searcher去查1-120條結果,Searcher其實是進行了2次搜索過程:頭100條取完后,緩存結果用完,Searcher重新檢索
再構造一個200條的結果緩存,依此類推,400條緩存,800條緩存。由于每次Searcher對象消失后,這些緩存也訪問那不到了,你有可能想將結果
記錄緩存下來,緩存數盡量保證在100以下以充分利用首次的結果緩存,不讓Lucene浪費多次檢索,而且可以分級進行結果緩存。

Lucene的另外一個特點是在收集結果的過程中將匹配度低的結果自動過濾掉了。這也是和數據庫應用需要將搜索的結果全部返回不同之處。

剛剛開始學Lucene,看的是Lucene in
Action。順著看下去,很自然的就是使用Hits來訪問Search的結果。但是使用起來,發現Search的速度是很快,不過如果結果很多的話(比 如1W個),通過Hits訪問所有的結果速度非常慢,就是簡單地從每個結果中讀一個Field,在我的機器上用了接近2分鐘。因為我的應用索引的只是我的 數據的兩個域包含文本信息的域,我本希望通過Lucene查找出符合需求的數據ID,再通過ID去判斷數據庫中的其他域來決定最終的結果。這樣連取ID就 需要2分鐘,我的應用可受不了。

第一個想到的方法是把我的全部數據域都做成Lucene的索引,然后全部通過Lucene去搜索。但是由于我的很多域是數字,全部轉換成 Lucene能接受的字符串,感覺性能不會好。另外如果我想針對搜索的結果做統計,也沒法避免需要遍歷全部的搜索結果,如果1W個結果就需要2分鐘的話, 就算不用處理其他的域,也是不能忍受的。

開源軟件的好處就是可以讀代碼。通過閱讀Hits的代碼,終于找到了解決問題的辦法。

Lucene
的代碼看起來并不是特別Professional。比如下面這兩個Hits的初始化函數。首先里面的q,s,f什么的讓人看起來就不是太舒服(其他的代碼 里還用i,j做循環變量)。其次這兩個函數只有o那一個賦值不一樣,明顯應該只寫一個,讓另一個來調用。最后程序里面直接用了50這個常數,編程的大 忌。(50在其他函數里面也有)

Hits(Searcher s, Query q, Filter f) throws IOException {
    weight =
q.weight(s);
    searcher =
s;
    filter =
f;
    nDeletions =
countDeletions(s);
   
getMoreDocs(50); // retrieve 100 initially
   
lengthAtStart = length;
  }

  Hits(Searcher s, Query q, Filter f, Sort o)
throws IOException {
    weight =
q.weight(s);
    searcher =
s;
    filter =
f;
    sort =
o;
    nDeletions =
countDeletions(s);
   
getMoreDocs(50); // retrieve 100 initially
   
lengthAtStart = length;
  }
通過這兩個函數,應該看出Hits初始化的時候只調入了前100個文檔。

一般我們是通過Document doc(int
n)函數來訪問的。這個函數里面先判斷了有多少數據已經被調入了,如果要訪問的數據不在,就去調用getMoreDocs函數,getMoreDocs會 取得需要的2倍文檔進來。

但是getMoreDocs的代碼比較讓人疑惑,里面一段代碼是這樣的:
    int n = min
* 2;    //
double # retrieved
    TopDocs
topDocs = (sort == null) ? searcher.search(weight, filter, n) :
searcher.search(weight, filter, n, sort);
這不成了每次翻倍的時候都要去調search重新查找嗎?除非search里面有緩存,否則性能一定指數下降啊!
實際上Hits最終使用的也是TopDocs,Searcher組合來實現輸出結果,那不如我們來直接使用下層一點的對象了。我原來的代碼是:

Hits hits = searcher.search(query);
for( int i=0;i<hits .length();i++) {
    Document doc
= hits .doc(i );
   
szTest.add(doc);
}
現在改為:
TopDocs topDoc = searcher.search(query.weight(searcher), null,
100000);//注意最后一個參數,是search返回的結果數量,應該比你最大可能返回的數量大,否則ScoreDoc里面就是你設置的數量。

ScoreDoc[] scoreDocs = topDoc.scoreDocs;
for( int i=0;i<scoreDocs.length;i++) {
    Document doc
= searcher.doc(scoreDocs[i].doc );
   
szTest.add(doc);
}
結果把12000個ID加入ArrayList用時0.4秒,快了幾百倍。

等等,還沒完。
我只需要ID字段,但是返回整個Doc,其他兩個文本Field也返回了。因為Lucene是倒索引保存信息的,每一個文本Field需要重新組合成原始 的字符串,這也是要耗時間的。searcher的doc函數有一個可以限定只取部分域的:

Document doc(int n, FieldSelector fieldSelector)

我下面定義一個FieldSelector,只取某一個給定名字的Field
class SpecialFieldSelector implements FieldSelector {
    protected
String m_szFieldName;
    public
SpecialFieldSelector( String szFieldName ) {
       
m_szFieldName = szFieldName;
    }
   
    public
FieldSelectorResult accept(String fieldName) {
       
if( fieldName.equalsIgnoreCase(m_szFieldName)) {
           
return  FieldSelectorResult.LOAD;
       
}
       
else {
           
return  FieldSelectorResult.NO_LOAD;
       
}
   
}   
}
再修改我的代碼:
ScoreDoc[] scoreDocs = topDoc.scoreDocs;
ArrayList<Document> szTest = new
ArrayList<Document>();
FieldSelector fieldSelector = new
SpecialFieldSelector(FIELD_ID);
for( int i=0;i<scoreDocs.length;i++) {
       
Document doc = searcher.doc(scoreDocs[i].doc, fieldSelector);
       
szTest.add(doc);
}
現在返回1.2W個ID耗時0.25秒。雖然比前面只少了大約150毫秒,但是是接近40%的提高了,在負載比較大的應用中還是很重要的。



注:
   有些可以借鑒的