posts - 30,  comments - 85,  trackbacks - 0

          Lucene 的學習

          ?????? 通過這幾天的看書和學習,對 Lucene 有了更進一步的認識,所以總結一下這些天的學習成果把 Lucene 的學習心得也學出來。

          1????????? Lucene 的認識

          提到 Lucene 很多人都知道這個開源的搜索工具,其魅力也是很大的。它讓我們對搜索引擎的認識不在那么神秘,也不會在覺得百度和 google 的技術多么的高深沒測,其實其原理都是一樣的,只是他們要做的更好,走的更遠罷了。

          Lucene 可以對任何的數據做索引和搜索,說這樣的話其實不過分,真的就是這樣,只要你能處理好這些數據,交給 Lucene 去建立索引它都可以幫你把這些數據給檢索出來,是不是很好玩了。真正好玩的地方還在后面呢。

          2????????? Lucene 的學習

          前面已經對 Lucene 有了一些了解,現在我們想象它怎么去搜索這些數據呢,如果知道倒排索引,你就知道了,其實 lucene 檢索的是它自己建立的索引,從索引中的到數據的指針,從而得到數據。其實就這么簡單。

          提到索引,現在的索引技術中有:倒排索引、后綴數組和簽名文件這三種,其中后綴數組這種技術雖然檢索速度也很快,但是它的數據結構構造和維護都是相當麻煩的所以不可取了。我也懶得去看了。至于簽名文件嘛,那是 80 年代的玩意了,現在已經過時了。現在可是倒排索引的天下啊!相信百度和 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 的搜索

          ?

          以上完成了索引的建立和一些關于索引的知識,但是光有索引是不行的,我們真正要做的檢索,這才是我們的關鍵。現在我們看看 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 安文豪 閱讀(4264) 評論(11)  編輯  收藏

          FeedBack:
          # re: Lucene的學習
          2006-09-15 18:23 | stoneshao
          lucene對中文的支持不行,它沒有中文的詞庫:(  回復  更多評論
            
          # re: Lucene的學習
          2006-09-16 12:40 | 讀者
          你的說法是錯的,lucene對中文是支持的。從二分詞來說,二分詞是實現中文檢索最簡單的方法。當然還有智能詞典分詞。相信百度就是這樣做的。  回復  更多評論
            
          # re: Lucene的學習
          2006-09-19 10:52 | =====ART OF JAVA=====
          車東寫過的  回復  更多評論
            
          # re: Lucene的學習
          2006-09-20 10:28 | guest
          看了后很有收獲
          field中的unstored之類在高版本的lucene中Deprecated  回復  更多評論
            
          # re: Lucene的學習
          2006-11-22 09:12 | liu
          不錯 介紹的很全面,對于應用足夠了。  回復  更多評論
            
          # re: Lucene的學習
          2006-11-22 09:14 | liu
          CJKAnalyzer 就是專門針對亞洲國家(中國、小日本、和韓國)的字庫進行切分詞的  回復  更多評論
            
          # re: Lucene的學習
          2007-06-20 12:43 | manus
          呵呵 恩 寫的不錯 支持下  回復  更多評論
            
          # re: Lucene的學習
          2007-10-19 16:26 | gw_noah@126.com
          你的這篇文章寫的太好了
          你的這篇文章寫的太好了
          你的這篇文章寫的太好了
          你的這篇文章寫的太好了
          你的這篇文章寫的太好了  回復  更多評論
            
          # re: Lucene的學習
          2008-03-07 22:54 | sly@167.com
          寫得很好,大哥受用拉  回復  更多評論
            
          # re: Lucene的學習
          2009-05-07 20:09 | 路過
          寫的不錯 支持下   回復  更多評論
            
          # re: Lucene的學習
          2011-03-20 21:33 | sgz
          問個問題,索引創建和搜索支不支持遠程啊,就是我想把索引庫創建到另外一臺機子上去  回復  更多評論
            

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           

          <2006年11月>
          2930311234
          567891011
          12131415161718
          19202122232425
          262728293012
          3456789

          常用鏈接

          留言簿(6)

          隨筆檔案(28)

          文章分類(3)

          文章檔案(4)

          最新隨筆

          搜索

          •  

          積分與排名

          • 積分 - 86635
          • 排名 - 668

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 华池县| 邳州市| 和顺县| 鹤峰县| 明溪县| 泽普县| 东安县| 裕民县| 台州市| 郯城县| 丹巴县| 皋兰县| 呼图壁县| 景谷| 肃宁县| 白朗县| 永仁县| 鄄城县| 泰兴市| 馆陶县| 兴业县| 东台市| 姚安县| 虹口区| 开阳县| 海宁市| 西昌市| 武陟县| 华宁县| 扎鲁特旗| 保康县| 巴马| 花垣县| 定日县| 军事| 霍城县| 民和| 宣武区| 灵武市| 张家川| 拉孜县|