【Java】Lucene檢索引擎詳解

基於Java的全文索引/檢索引擎——Lucene

  Lucene不是一個完整的全文索引應用,而是是一個用Java寫的全文索引引擎工具包,它能夠方便的嵌入到各類應用中實現針對應用的全文索引/檢索功能。html

  Lucene的做者:Lucene的貢獻者Doug Cutting是一位資深全文索引/檢索專家,曾經是V-Twin搜索引擎(Apple的Copland操做系統的成就之一)的主要開發者,後在Excite擔任高級系統架構設計師,目前從事於一些INTERNET底層架構的研究。他貢獻出的Lucene的目標是爲各類中小型應用程序加入全文檢索功能。前端

  Lucene的發展歷程:早先發布在做者本身的www.lucene.com,後來發佈在SourceForge,2001年年末成爲APACHE基金會jakarta的一個子項目:http://jakarta.apache.org/lucene/java

全文檢索的實現機制

  Lucene的API接口設計的比較通用,輸入輸出結構都很像數據庫的表==>記錄==>字段,因此不少傳統的應用的文件、數據庫等均可以比較方便的映射到Lucene的存儲結構/接口中。整體上看:能夠先把Lucene當成一個支持全文索引的數據庫系統git

  比較一下Lucene和數據庫:web

Lucene 數據庫
索引數據源:doc(field1,field2...) doc(field1,field2...)
\ indexer /
_____________
| Lucene Index|
--------------
/ searcher \
結果輸出:Hits(doc(field1,field2) doc(field1...))
 索引數據源:record(field1,field2...) record(field1..)
\ SQL: insert/
_____________
| DB Index |
-------------
/ SQL: select \
結果輸出:results(record(field1,field2..) record(field1...))
Document:一個須要進行索引的「單元」
一個Document由多個字段組成
Record:記錄,包含多個字段
Field:字段 Field:字段
Hits:查詢結果集,由匹配的Document組成 RecordSet:查詢結果集,由多個Record組成

  一般比較厚的書籍後面經常附關鍵詞索引表(好比:北京:12, 34頁, 上海:3,77頁……),它可以幫助讀者比較快地找到相關內容的頁碼。算法

  而數據庫索引可以大大提升查詢的速度原理也是同樣,想像一下經過書後面的索引查找的速度要比一頁一頁地翻內容高多少倍……而索引之因此效率高,另一個緣由是它是排好序的。對於檢索系統來講核心是一個排序問題。數據庫

  創建一個高效檢索系統的關鍵是創建一個相似於科技索引同樣的反向索引機制,將數據源(好比多篇文章)排序順序存儲的同時,有另一個排好序的關鍵詞列表,用於存儲關鍵詞==>文章映射關係,利用這樣的映射關係索引:[關鍵詞==>出現關鍵詞的文章編號,出現次數(甚至包括位置:起始偏移量,結束偏移量),出現頻率],檢索過程就是把模糊查詢變成多個能夠利用索引的精確查詢的邏輯組合的過程。從而大大提升了多關鍵詞查詢的效率,因此,全文檢索問題歸結到最後是一個排序問題。express

   全文檢索和數據庫應用最大的不一樣在於:讓最相關的頭100條結果知足98%以上用戶的需求apache

  Lucene的創新之處:數組

  大部分的搜索(數據庫)引擎都是用B樹結構來維護索引,索引的更新會致使大量的IO操做,Lucene在實現中,對此稍微有所改進:不是維護一個索引文件,而是在擴展索引的時候不斷建立新的索引文件,而後按期的把這些新的小索引文件合併到原先的大索引中(針對不一樣的更新策略,批次的大小能夠調整),這樣在不影響檢索的效率的前提下,提升了索引的效率。

  Lucene和其餘一些全文檢索系統/應用的比較:

  Lucene 其餘開源全文檢索系統
增量索引和批量索引 能夠進行增量的索引(Append),能夠對於大量數據進行批量索引,而且接口設計用於優化批量索引和小批量的增量索引。 不少系統只支持批量的索引,有時數據源有一點增長也須要重建索引。
數據源 Lucene沒有定義具體的數據源,而是一個文檔的結構,所以能夠很是靈活的適應各類應用(只要前端有合適的轉換器把數據源轉換成相應結構), 不少系統只針對網頁,缺少其餘格式文檔的靈活性。
索引內容抓取 Lucene的文檔是由多個字段組成的,甚至能夠控制那些字段須要進行索引,那些字段不須要索引,近一步索引的字段也分爲須要分詞和不須要分詞的類型:
   須要進行分詞的索引,好比:標題,文章內容字段
   不須要進行分詞的索引,好比:做者/日期字段
缺少通用性,每每將文檔整個索引了
語言分析 經過語言分析器的不一樣擴展實現:
能夠過濾掉不須要的詞:an the of 等,
西文語法分析:將jumps jumped jumper都歸結成jump進行索引/檢索
非英文支持:對亞洲語言,阿拉伯語言的索引支持
缺少通用接口實現
查詢分析 經過查詢分析接口的實現,能夠定製本身的查詢語法規則:
好比: 多個關鍵詞之間的 + - and or關係等
 
併發訪問 可以支持多用戶的使用  

關於亞洲語言的的切分詞問題(Word Segment)

  對於中文來講,全文索引首先還要解決一個語言分析的問題,對於英文來講,語句中單詞之間是自然經過空格分開的,但亞洲語言的中日韓文語句中的字是一個字挨一個,因此,首先要把語句中按「詞」進行索引的話,這個詞如何切分出來就是一個很大的問題。

  首先,確定不能用單個字符做(si-gram)爲索引單元,不然查「上海」時,不能讓含有「海上」也匹配。

  但一句話:「北京天安門」,計算機如何按照中文的語言習慣進行切分呢?
  「北京 天安門」 仍是「北 京 天安門」?讓計算機可以按照語言習慣進行切分,每每須要機器有一個比較豐富的詞庫纔可以比較準確的識別出語句中的單詞。

  另一個解決的辦法是採用自動切分算法:將單詞按照2元語法(bigram)方式切分出來,好比:"北京天安門" ==> "北京 京天 天安 安門"。

  這樣,在查詢的時候,不管是查詢"北京" 仍是查詢"天安門",將查詢詞組按一樣的規則進行切分:"北京","天安安門",多個關鍵詞之間按與"and"的關係組合,一樣可以正確地映射到相應的索引中。

  這種方式對於其餘亞洲語言:韓文,日文都是通用的。

  基於自動切分的最大優勢是沒有詞表維護成本,實現簡單,缺點是索引效率低,但對於中小型應用來講,基於2元語法的切分仍是夠用的。

  基於2元切分後的索引通常大小和源文件差很少,而對於英文,索引文件通常只有原文件的30%-40%不一樣,

  自動切分 詞表切分
實現 實現很是簡單 實現複雜
查詢 增長了查詢分析的複雜程度, 適於實現比較複雜的查詢語法規則
存儲效率 索引冗餘大,索引幾乎和原文同樣大 索引效率高,爲原文大小的30%左右
維護成本 無詞表維護成本 詞表維護成本很是高:中日韓等語言須要分別維護。
還須要包括詞頻統計等內容
適用領域 嵌入式系統:運行環境資源有限
分佈式系統:無詞表同步問題
多語言環境:無詞表維護成本
對查詢和存儲效率要求高的專業搜索引擎

  目前比較大的搜索引擎的語言分析算法通常是基於以上2個機制的結合。

lucene的組成結構

  對於外部應用來講索引模塊(index)和檢索模塊(search)是主要的外部應用入口

org.apache.Lucene.search/ 搜索入口
org.apache.Lucene.index/ 索引入口
org.apache.Lucene.analysis/ 語言分析器
org.apache.Lucene.queryParser/ 查詢分析器
org.apache.Lucene.document/ 存儲結構
org.apache.Lucene.store/  底層IO/存儲結構
org.apache.Lucene.util/ 一些公用的數據結構

  索引過程:從命令行讀取文件名(多個),將文件分路徑(path字段)和內容(body字段)2個字段進行存儲,並對內容進行全文索引:

  索引的單位是Document對象,每一個Document對象包含多個字段Field對象,針對不一樣的字段屬性和數據輸出的需求,對字段還能夠選擇不一樣的索引/存儲字段規則,

  列表以下:

方法 切詞 索引 存儲 用途
Field.Text(String name, String value) Yes Yes Yes 切分詞索引並存儲,好比:標題,內容字段
Field.Text(String name, Reader value) Yes Yes No 切分詞索引不存儲,好比:META信息,
不用於返回顯示,但須要進行檢索內容
Field.Keyword(String name, String value) No Yes Yes 不切分索引並存儲,好比:日期字段
Field.UnIndexed(String name, String value) No No Yes 不索引,只存儲,好比:文件路徑
Field.UnStored(String name, String value) Yes Yes No 只全文索引,不存儲

  索引過程當中能夠看到:

  • 語言分析器提供了抽象的接口,所以語言分析(Analyser)是能夠定製的,雖然lucene缺省提供了2個比較通用的分析器SimpleAnalyser和StandardAnalyser,這2個分析器缺省都不支持中文,因此要加入對中文語言的切分規則,須要修改這2個分析器。
  • Lucene並無規定數據源的格式,而只提供了一個通用的結構(Document對象)來接受索引的輸入,所以輸入的數據源能夠是:數據庫,WORD文檔,PDF文檔,HTML文檔……只要可以設計相應的解析轉換器將數據源構形成成Docuement對象便可進行索引。
  • 對於大批量的數據索引,還能夠經過調整IndexerWrite的文件合併頻率屬性(mergeFactor)來提升批量索引的效率。

  檢索過程和結果顯示:

  • 搜索結果返回的是Hits對象,能夠經過它再訪問Document==>Field中的內容。
  • 假設根據body字段進行全文檢索,能夠將查詢結果的path字段和相應查詢的匹配度(score)打印出來
  • 在整個檢索過程當中,語言分析器,查詢分析器,甚至搜索器(Searcher)都是提供了抽象的接口,能夠根據須要進行定製。

簡化的查詢分析器

  目前LUCENE支持的語法:

Query ::= ( Clause )*
Clause ::= ["+", "-"] [<TERM> ":"] ( <TERM> | "(" Query ")")

  中間的邏輯包括:and or + - &&||等符號,並且還有"短語查詢"和針對西文的前綴/模糊查詢等,對於通常應用來講,這些功能有一些華而不實,其實可以實現目前相似於Google的查詢語句分析功能其實對於大多數用戶來講已經夠了。

  因此,Lucene早期版本的QueryParser還是比較好的選擇。

添加修改刪除指定記錄(Document)

  Lucene提供了索引的擴展機制,所以索引的動態擴展應該是沒有問題的,而指定記錄的修改也彷佛只能經過記錄的刪除,而後從新加入實現。

  如何刪除指定的記錄呢?

  刪除的方法也很簡單,只是須要在索引時根據數據源中的記錄ID專門另建索引,而後利用IndexReader.delete(Termterm)方法經過這個記錄ID刪除相應的Document。

根據某個字段值的排序功能

  lucene缺省是按照本身的相關度算法(score)進行結果排序的,但可以根據其餘字段進行結果排序是一個在LUCENE的開發郵件列表中常常提到的問題,不少原先基於數據庫應用都須要除了基於匹配度(score)之外的排序功能。

  而從全文檢索的原理咱們能夠了解到,任何不基於索引的搜索過程效率都會致使效率很是的低,若是基於其餘字段的排序須要在搜索過程當中訪問存儲字段,速度回大大下降,所以很是是不可取的。

  但這裏也有一個折中的解決方法:在搜索過程當中可以影響排序結果的只有索引中已經存儲的docID和score這2個參數,因此,基於score之外的排序,其實能夠經過將數據源預先排好序,而後根據docID進行排序來實現。這樣就避免了在LUCENE搜索結果外對結果再次進行排序和在搜索過程當中訪問不在索引中的某個字段值。

更通用的輸入輸出接口

  雖然lucene沒有定義一個肯定的輸入文檔格式,但愈來愈多的人想到使用一個標準的中間格式做爲Lucene的數據導入接口,而後其餘數據,好比PDF只須要經過解析器轉換成標準的中間格式就能夠進行數據索引了。這個中間格式主要以XML爲主,相似實現已經不下4,5個:

數據源: WORD       PDF     HTML    DB       other
\ | | | /
XML中間格式
|
Lucene INDEX

索引過程優化

  索引通常分2種狀況,一種是小批量的索引擴展,一種是大批量的索引重建。

  在索引過程當中,並非每次新的DOC加入進去索引都從新進行一次索引文件的寫入操做(文件I/O是一件很是消耗資源的事情)。

  Lucene先在內存中進行索引操做,並根據必定的批量進行文件的寫入。

  這個批次的間隔越大,文件的寫入次數越少,但佔用內存會不少。反之佔用內存少,但文件IO操做頻繁,索引速度會很慢。

  在IndexWriter中有一個MERGE_FACTOR參數能夠幫助你在構造索引器後根據應用環境的狀況充分利用內存減小文件的操做。

  根據經驗:缺省Indexer是每20條記錄索引後寫入一次,每將MERGE_FACTOR增長50倍,索引速度能夠提升1倍左右。

搜索過程優化

  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學到更多

  Luene的確是一個面向對象設計的典範

  • 全部的問題都經過一個額外抽象層來方便之後的擴展和重用:你能夠經過從新實現來達到本身的目的,而對其餘模塊則不須要;
  • 簡單的應用入口Searcher, Indexer,並調用底層一系列組件協同的完成搜索任務;
  • 全部的對象的任務都很是專注:好比搜索過程:QueryParser分析將查詢語句轉換成一系列的精確查詢的組合(Query),經過底層的索引讀取結構IndexReader進行索引的讀取,並用相應的打分器給搜索結果進行打分/排序等。全部的功能模塊原子化程度很是高,所以能夠經過從新實現而不須要修改其餘模塊。 
  • 除了靈活的應用接口設計,Lucene還提供了一些適合大多數應用的語言分析器實現(SimpleAnalyser,StandardAnalyser),這也是新用戶可以很快上手的重要緣由之一。

  這些優勢都是很是值得在之後的開發中學習借鑑的。做爲一個通用工具包,Lunece的確給予了須要將全文檢索功能嵌入到應用中的開發者不少的便利。

  此外,經過對Lucene的學習和使用,理解了爲何不少數據庫優化設計中要求,好比:

  • 儘量對字段進行索引來提升查詢速度,但過多的索引會對數據庫表的更新操做變慢,而對結果過多的排序條件,實際上每每也是性能的殺手之一。
  • 不少商業數據庫對大批量的數據插入操做會提供一些優化參數,這個做用和索引器的merge_factor的做用是相似的,
  • 20%/80%原則:查的結果多並不等於質量好,尤爲對於返回結果集很大,如何優化這頭幾十條結果的質量每每纔是最重要的。
  • 儘量讓應用從數據庫中得到比較小的結果集,由於即便對於大型數據庫,對結果集的隨機訪問也是一個很是消耗資源的操做。

Lucene學習示例

  1.HelloWorld入門

  1 import java.io.BufferedReader;
  2 import java.io.File;
  3 import java.io.FileReader;
  4 import java.io.IOException;
  5 
  6 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  7 import org.apache.lucene.document.Document;
  8 import org.apache.lucene.document.Field;
  9 import org.apache.lucene.index.IndexReader;
 10 import org.apache.lucene.index.IndexWriter;
 11 import org.apache.lucene.index.IndexWriterConfig;
 12 import org.apache.lucene.queryParser.QueryParser;
 13 import org.apache.lucene.search.IndexSearcher;
 14 import org.apache.lucene.search.Query;
 15 import org.apache.lucene.search.ScoreDoc;
 16 import org.apache.lucene.search.TopDocs;
 17 import org.apache.lucene.store.Directory;
 18 import org.apache.lucene.store.FSDirectory;
 19 import org.apache.lucene.util.Version;
 20 
 21 /* 【Lucene3.6.2入門系列】第01節_HelloWord 
 22  * @see 這裏只需用到一個lucene-core-3.6.2.jar 
 23  * @see Lucene官網:http://lucene.apache.org 
 24  * @see Lucene下載:http://archive.apache.org/dist/lucene/java/ 
 25  * @see Lucene文檔:http://wiki.apache.org/lucene-java/ 
 26  * @see ------------------------------------------------------------------------------------------------------------- 
 27  * @see 1)對於全文搜索工具,都是由索引、分詞、搜索三部分組成 
 28  * @see 2)被存儲和被索引,是兩個獨立的概念 
 29  * @see ------------------------------------------------------------------------------------------------------------- 
 30  * @see 域的存儲選項 
 31  * @see Field.Store.YES--會把該域中的內容存儲到文件中,方便進行文本的還原 
 32  * @see Field.Store.NO---表示該域中的內容不存儲到文件中,但容許被索引,且內容沒法徹底還原(doc.get("##")) 
 33  * @see ------------------------------------------------------------------------------------------------------------- 
 34  * @see 域的索引選項 
 35  * @see Field.Index.ANALYZED----------------進行分詞和索引,適用於標題、內容等 
 36  * @see Field.Index.NOT_ANALYZED------------進行索引但不分詞(如身份證號、姓名、ID等),適用於精確搜索 
 37  * @see Field.Index.ANALYZED_NOT_NORMS------進行分詞可是不存儲norms信息,這個norms中包括了建立索引的時間和權值等信息 
 38  * @see Field.Index.NOT_ANALYZED_NOT_NORMS--即不進行分詞也不存儲norms信息 
 39  * @see Field.Index.NO----------------------不進行索引 
 40  * @see norms:當數據被搜索出來後,便涉及到排序的問題,而排序是有一些評分規則的,因而NORMS中就存儲了這些排序的信息 
 41  * @see -------------------------------------------------------------------------------------------------------------  
 42  * @see 域選項最佳實踐 
 43  * @see Field.Store   Field.Index              域值 
 44  * @see       YES     NOT_ANALYZED_NOT_NORMS   標識符(主鍵、文件名),電話號碼,身份證號,姓名,日期 
 45  * @see       YES     ANALYZED                 文檔標題和摘要 
 46  * @see        NO     ANALYZED                 文檔正文 
 47  * @see        NO     NOT_ANALYZED             隱藏關鍵字 
 48  * @see       YES     NO                       文檔類型,數據庫主鍵(不進行索引) 
 49  * @see ------------------------------------------------------------------------------------------------------------- 
 50  * @create Jun 29, 2012 4:20:19 PM 
 51  * @author 玄玉<http://blog.csdn.net/jadyer> 
 52  */
 53 public class Lucene_01_HelloWord {
 54     private static final String PATH_OF_FILE = "E:/lucene_test/01_file/";   // 待索引文件的目錄
 55     private static final String PATH_OF_INDEX = "E:/lucene_test/01_index/"; // 存放索引文件的目錄2
 56 
 57     /**
 58      * 測試時,要在E:/lucene_test/01_file/文件夾中準備幾個包含內容的文件(好比txt格式的)
 59      * 而後先執行createIndex()方法,再執行searchFile()方法,最後觀看控制檯輸出便可
 60      */
 61     public static void main(String[] args) {
 62         Lucene_01_HelloWord instance = new Lucene_01_HelloWord();
 63         instance.createIndex();
 64         instance.searchFile();
 65     }
 66 
 67     /**
 68      * 建立索引
 69      * 
 70      * @see ---------------------------------------------------------------------------------------------------------
 71      * @see 一、建立Directory-----------------指定索引被保存的位置
 72      * @see 二、建立IndexWriter---------------經過IndexWriter寫索引
 73      * @see 三、建立Document對象---------------咱們索引的有多是一段文本or數據庫中的一張表
 74      * @see 四、爲Document添加Field------------至關於Document的標題、大小、內容、路徑等等,兩者相似於數據庫表中每條記錄和字段的關係
 75      * @see 五、經過IndexWriter添加文檔到索引中
 76      * @see 六、關閉IndexWriter----------------用完IndexWriter以後,必須關閉之
 77      * @see ---------------------------------------------------------------------------------------------------------
 78      * @see _0.fdt和_0.fdx文件--保存域中所存儲的數據(Field.Store.YES條件下的)
 79      * @see _0.fnm文件----------保存域選項的數據(即new Field(name, value)中的name)
 80      * @see _0.frq文件----------記錄相同的文件(或查詢的關鍵字)出現的次數,它是用來作評分和排序的
 81      * @see _0.nrm文件----------存儲一些評分信息
 82      * @see _0.prx文件----------記錄偏移量
 83      * @see _0.tii和_0.tis文件--存儲索引裏面的全部內容信息
 84      * @see segments_1文件------它是段文件,Lucene首先會到段文件中查找相應的索引信息
 85      * @see ---------------------------------------------------------------------------------------------------------
 86      */
 87     private void createIndex() {
 88         Directory directory = null;
 89         IndexWriter writer = null;
 90         Document doc = null;
 91         try {
 92             // FSDirectory會根據當前的運行環境打開一個合理的基於File的Directory(若在內存中建立索引則new RAMDirectory())
 93             // 這裏是在硬盤上"E:/lucene_test/01_index/"文件夾中建立索引
 94             directory = FSDirectory.open(new File(PATH_OF_INDEX));
 95             // 因爲Lucene2.9以後,其索引的格式就不會再兼容Lucene的全部版本了,因此在建立索引前,要指定其所匹配的Lucene版本號
 96             // 這裏經過IndexWriterConfig()構造方法的Version.LUCENE_36參數值指明索引所匹配的版本號,並使用了Lucene的標準分詞器
 97             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 98             for (File file : new File(PATH_OF_FILE).listFiles()) {
 99                 doc = new Document();
100                 // 把內容添加到索引域中,即爲該文檔存儲信息,供未來搜索時使用(下面的寫法,其默認爲Field.Store.NO和Field.Index.ANALYZED)
101                 // 若是咱們想把content的內容也存儲到硬盤上,那就須要先把file轉換成字符串,而後按照"fileName"的存儲方式加到Field中
102                 // 咱們能夠用commons-io-2.3.jar提供的FileUtils.readFileToString(file),這是很方便的工具包,有了它幾乎都不用手寫任何的IO方法了
103                 // doc.add(new Field("content", FileUtils.readFileToString(file), Field.Store.YES, Field.Index.ANALYZED));
104                 doc.add(new Field("content", new FileReader(file)));
105                 // Field.Store.YES-----------這裏是將文件的全名存儲到硬盤中
106                 // Field.Index.NOT_ANALYZED--這裏是不對文件名進行分詞
107                 doc.add(new Field("fileName", file.getName(), Field.Store.YES, Field.Index.NOT_ANALYZED));
108                 doc.add(new Field("filePath", file.getAbsolutePath(), Field.Store.YES, Field.Index.NOT_ANALYZED));
109                 // 經過IndexWriter添加文檔到索引中
110                 writer.addDocument(doc);
111             }
112         }
113         catch (Exception e) {
114             System.out.println("建立索引的過程當中遇到異常,堆棧軌跡以下");
115             e.printStackTrace();
116         }
117         finally {
118             if (null != writer) {
119                 try {
120                     writer.close(); // IndexWriter在用完以後必定要關閉
121                 }
122                 catch (IOException ce) {
123                     System.out.println("關閉IndexWriter時遇到異常,堆棧軌跡以下");
124                     ce.printStackTrace();
125                 }
126             }
127         }
128     }
129 
130     private String getContentFromFile(File myFile) {
131         StringBuffer sb = new StringBuffer();
132         if (!myFile.exists()) {
133             return "";
134         }
135         try {
136             BufferedReader in = new BufferedReader(new FileReader(myFile));
137             String str;
138             while ((str = in.readLine()) != null) {
139                 sb.append(str);
140             }
141             in.close();
142         }
143         catch (IOException e) {
144             e.getStackTrace();
145         }
146         return sb.toString();
147     }
148 
149     /**
150      * 搜索文件
151      * 
152      * @see 一、建立Directory
153      * @see 二、建立IndexReader
154      * @see 三、根據IndexReader建立IndexSearcher
155      * @see 四、建立搜索的Query
156      * @see 五、根據searcher搜索並返回TopDocs
157      * @see 六、根據TopDocs獲取ScoreDoc對象
158      * @see 七、根據searcher和ScoreDoc對象獲取具體的Document對象
159      * @see 八、根據Document對象獲取須要的值
160      * @see 九、關閉IndexReader
161      */
162     private void searchFile() {
163         IndexReader reader = null;
164         try {
165             reader = IndexReader.open(FSDirectory.open(new File(PATH_OF_INDEX)));
166             IndexSearcher searcher = new IndexSearcher(reader);
167             // 建立基於Parser搜索的Query,建立時需指定其"搜索的版本,默認搜索的域,分詞器"....這裏的域指的是建立索引時Field的名字
168             QueryParser parser = new QueryParser(Version.LUCENE_36, "content", new StandardAnalyzer(Version.LUCENE_36));
169             Query query = parser.parse("java");       // 指定==>搜索域爲content(即上一行代碼指定的"content")中包含"java"的文檔
170             TopDocs tds = searcher.search(query, 10); // 第二個參數指定搜索後顯示的條數,若查到5條則顯示爲5條,查到15條則只顯示10條
171             ScoreDoc[] sds = tds.scoreDocs;           // TopDocs中存放的並非咱們的文檔,而是文檔的ScoreDoc對象
172             for (ScoreDoc sd : sds) {                   // ScoreDoc對象至關於每一個文檔的ID號,咱們就能夠經過ScoreDoc來遍歷文檔
173                 Document doc = searcher.doc(sd.doc);  // sd.doc獲得的是文檔的序號
174                 System.out.println(doc.get("fileName") + "[" + doc.get("filePath") + "]"); // 輸出該文檔所存儲的信息
175             }
176         }
177         catch (Exception e) {
178             System.out.println("搜索文件的過程當中遇到異常,堆棧軌跡以下");
179             e.printStackTrace();
180         }
181         finally {
182             if (null != reader) {
183                 try {
184                     reader.close();
185                 }
186                 catch (IOException e) {
187                     System.out.println("關閉IndexReader時遇到異常,堆棧軌跡以下");
188                     e.printStackTrace();
189                 }
190             }
191         }
192     }
193 
194 }
View Code

  2.針對索引文件的CRUD

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.text.SimpleDateFormat;
  4 import java.util.Date;
  5 
  6 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  7 import org.apache.lucene.document.Document;
  8 import org.apache.lucene.document.Field;
  9 import org.apache.lucene.document.NumericField;
 10 import org.apache.lucene.index.IndexReader;
 11 import org.apache.lucene.index.IndexWriter;
 12 import org.apache.lucene.index.IndexWriterConfig;
 13 import org.apache.lucene.index.Term;
 14 import org.apache.lucene.search.IndexSearcher;
 15 import org.apache.lucene.search.Query;
 16 import org.apache.lucene.search.ScoreDoc;
 17 import org.apache.lucene.search.TermQuery;
 18 import org.apache.lucene.search.TopDocs;
 19 import org.apache.lucene.store.Directory;
 20 import org.apache.lucene.store.FSDirectory;
 21 import org.apache.lucene.util.Version;
 22 
 23 public class Lucene_02_HelloIndex {
 24     /**
 25      * 【Lucene3.6.2入門系列】第02節_針對索引文件的CRUD
 26      * 
 27      * @see =============================================================================================================
 28      * @see Lucene官網:http://lucene.apache.org
 29      * @see Lucene下載:http://archive.apache.org/dist/lucene/java/
 30      * @see Lucene文檔:http://wiki.apache.org/lucene-java/
 31      * @see =============================================================================================================
 32      * @see 使用Luke查看分詞信息(http://code.google.com/p/luke/)
 33      * @see 1)引言:每個Lucene版本都會有一個相應的Luke文件
 34      * @see 2)打開:雙擊或java -jar lukeall-3.5.0.jar
 35      * @see 3)選擇索引的存放目錄後點擊OK便可
 36      * @see 7)若是咱們的索引有改變,能夠點擊右側的Re-open按鈕從新載入索引
 37      * @see 4)Luke界面右下角的Top ranking terms窗口中顯示的就是分詞信息。其中Rank列表示出現頻率
 38      * @see 5)Luke菜單下的Documents選項卡中顯示的就是文檔信息,咱們能夠根據文檔序號來瀏覽(點擊向左和向右的方向箭頭)
 39      * @see 6)Luke菜單下的Search選項卡中能夠根據咱們輸入的表達式來查文檔內容
 40      * @see 好比在Enter search expression here:輸入content:my,再在右側點擊一個黑色粗體字的Search大按鈕便可
 41      * @see =============================================================================================================
 42      * @create Jun 30, 2012 4:34:09 PM
 43      * @author 玄玉<http://blog.csdn.net/jadyer>
 44      */
 45     /*
 46      * 定義一組數據,用來演示搜索(這裏有一封郵件爲例)
 47      * 假設每個變量表明一個Document,這裏就定義了6個Document
 48      */
 49     // 郵件編號
 50     private String[] ids = { "1", "2", "3", "4", "5", "6" };
 51     // 郵件主題
 52     private String[] names = { "Michael", "Scofield", "Tbag", "Jack", "Jade", "Jadyer" };
 53     // 郵件地址
 54     private String[] emails = { "aa@jadyer.us", "bb@jadyer.cn", "cc@jadyer.cc", "dd@jadyer.tw", "ee@jadyer.hk", "ff@jadyer.me" };
 55     // 郵件內容
 56     private String[] contents = { "my blog", "my website", "my name", "I am JavaDeveloper", "I am from Haerbin", "I like Lucene" };
 57     // 郵件附件(爲數字和日期加索引,與,字符串加索引的方式不一樣)
 58     private int[] attachs = { 9, 3, 5, 4, 1, 2 };
 59     // 郵件日期
 60     private Date[] dates = new Date[ids.length];
 61     // 它的建立是比較耗時耗資源的,因此這裏只讓它建立一次,此時reader處於整個生命週期中,實際應用中也可能直接放到ApplicationContext裏面
 62     private static IndexReader reader = null;
 63     private Directory directory = null;
 64 
 65     public static void main(String[] args) {
 66         Lucene_02_HelloIndex instance = new Lucene_02_HelloIndex();
 67         instance.createIndex();
 68         instance.searchFile();
 69         instance.updateIndex();
 70         instance.getDocsCount();
 71     }
 72 
 73     public Lucene_02_HelloIndex() {
 74         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
 75         try {
 76             dates[0] = (Date) sdf.parse("20120601");
 77             dates[1] = (Date) sdf.parse("20120603");
 78             dates[2] = (Date) sdf.parse("20120605");
 79             dates[3] = (Date) sdf.parse("20120607");
 80             dates[4] = (Date) sdf.parse("20120609");
 81             dates[5] = (Date) sdf.parse("20120611");
 82             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 83         }
 84         catch (Exception e) {
 85             e.printStackTrace();
 86         }
 87     }
 88 
 89     /**
 90      * 獲取IndexReader實例
 91      */
 92     private IndexReader getIndexReader() {
 93         try {
 94             if (reader == null) {
 95                 reader = IndexReader.open(directory);
 96             }
 97             else {
 98                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
 99                 // 若是當前reader在打開期間index發生改變,則打開並返回一個新的IndexReader,不然返回null
100                 IndexReader ir = IndexReader.openIfChanged(reader);
101                 if (ir != null) {
102                     reader.close(); // 關閉原reader
103                     reader = ir;    // 賦予新reader
104                 }
105             }
106             return reader;
107         }
108         catch (Exception e) {
109             e.printStackTrace();
110         }
111         return null; // 發生異常則返回null
112     }
113 
114     /**
115      * 經過IndexReader獲取文檔數量
116      */
117     public void getDocsCount() {
118         System.out.println("maxDocs:" + this.getIndexReader().maxDoc());
119         System.out.println("numDocs:" + this.getIndexReader().numDocs());
120         System.out.println("deletedDocs:" + this.getIndexReader().numDeletedDocs());
121     }
122 
123     /**
124      * 建立索引
125      */
126     public void createIndex() {
127         IndexWriter writer = null;
128         Document doc = null;
129         try {
130             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
131             writer.deleteAll();              // 建立索引以前,先把文檔清空掉
132             for (int i = 0; i < ids.length; i++) { // 遍歷ID來建立文檔
133                 doc = new Document();
134                 doc.add(new Field("id", ids[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
135                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
136                 doc.add(new Field("email", emails[i], Field.Store.YES, Field.Index.NOT_ANALYZED));
137                 doc.add(new Field("content", contents[i], Field.Store.NO, Field.Index.ANALYZED));
138                 doc.add(new NumericField("attach", Field.Store.YES, true).setIntValue(attachs[i]));        // 爲數字加索引(第三個參數指定是否索引)
139                 doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[i].getTime())); // 爲日期加索引
140                 /*
141                  * 創建索引時加權
142                  * 定義排名規則,即加權,這裏是爲指定郵件名結尾的emails加權
143                  */
144                 if (emails[i].endsWith("jadyer.cn")) {
145                     doc.setBoost(2.0f);
146                 }
147                 else if (emails[i].endsWith("jadyer.me")) {
148                     doc.setBoost(1.5f); // 爲文檔加權....默認爲1.0,權值越高則排名越高,顯示得就越靠前
149                 }
150                 else {
151                     doc.setBoost(0.5f); // 注意它的參數類型是Float
152                 }
153                 writer.addDocument(doc);
154             }
155         }
156         catch (Exception e) {
157             e.printStackTrace();
158         }
159         finally {
160             if (null != writer) {
161                 try {
162                     writer.close();
163                 }
164                 catch (IOException ce) {
165                     ce.printStackTrace();
166                 }
167             }
168         }
169     }
170 
171     /**
172      * 搜索文件
173      */
174     public void searchFile() {
175         IndexSearcher searcher = new IndexSearcher(this.getIndexReader());
176         Query query = new TermQuery(new Term("content", "my")); // 精確搜索:搜索"content"中包含"my"的文檔
177         try {
178             TopDocs tds = searcher.search(query, 10);
179             for (ScoreDoc sd : tds.scoreDocs) {
180                 Document doc = searcher.doc(sd.doc); // sd.doc獲得的是文檔的序號
181                 // doc.getBoost()獲得的權值與建立索引時設置的權值之間是不相搭的,建立索引時的權值的查看須要使用Luke工具
182                 // 之因此這樣,是由於這裏的Document對象(是獲取到的)與建立索引時的Document對象,不是同一個對象
183                 // sd.score獲得的是該文檔的評分,該評分規則的公式是比較複雜的,它主要與文檔的權值和出現次數成正比
184                 System.out.print("(" + sd.doc + "|" + doc.getBoost() + "|" + sd.score + ")" + doc.get("name") + "[" + doc.get("email") + "]-->");
185                 System.out.println(doc.get("id") + "," + doc.get("attach") + "," + new SimpleDateFormat("yyyyMMdd").format(new Date(Long.parseLong(doc.get("date")))));
186             }
187         }
188         catch (Exception e) {
189             e.printStackTrace();
190         }
191         finally {
192             if (null != searcher) {
193                 try {
194                     searcher.close();
195                 }
196                 catch (IOException e) {
197                     e.printStackTrace();
198                 }
199             }
200         }
201     }
202 
203     /**
204      * 更新索引
205      * 
206      * @see Lucene其實並未提供更新索引的方法,這裏的更新操做內部是先刪除再添加的方式
207      * @see 由於Lucene認爲更新索引的代價,與刪除後重建索引的代價,兩者是差很少的
208      */
209     public void updateIndex() {
210         IndexWriter writer = null;
211         Document doc = new Document();
212         try {
213             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
214             doc.add(new Field("id", "1111", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
215             doc.add(new Field("name", names[0], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
216             doc.add(new Field("email", emails[0], Field.Store.YES, Field.Index.NOT_ANALYZED));
217             doc.add(new Field("content", contents[0], Field.Store.NO, Field.Index.ANALYZED));
218             doc.add(new NumericField("attach", Field.Store.YES, true).setIntValue(attachs[0]));
219             doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[0].getTime()));
220             // 其實它會先刪除索引文檔中id爲1的文檔,而後再將這裏的doc對象從新索引,因此即使這裏的1!=1111,但它並不會報錯
221             // 因此在執行完該方法後:maxDocs=7,numDocs=6,deletedDocs=1,就是由於Lucene會先刪除再添加
222             writer.updateDocument(new Term("id", "1"), doc);
223         }
224         catch (Exception e) {
225             e.printStackTrace();
226         }
227         finally {
228             if (null != writer) {
229                 try {
230                     writer.close();
231                 }
232                 catch (IOException ce) {
233                     ce.printStackTrace();
234                 }
235             }
236         }
237     }
238 
239     /**
240      * 刪除索引
241      * 
242      * @see -----------------------------------------------------------------------------------------------------
243      * @see 在執行完該方法後,再執行本類的searchFile()方法,得知numDocs=5,maxDocs=6,deletedDocs=1
244      * @see 這說明此時刪除的文檔並無被徹底刪除,而是存儲在一個回收站中,它是能夠恢復的
245      * @see -----------------------------------------------------------------------------------------------------
246      * @see 從回收站中清空索引IndexWriter
247      * @see 對於清空索引,Lucene3.5以前叫作優化,調用的是IndexWriter.optimize()方法,但該方法已被禁用
248      * @see 由於optimize時它會所有更新索引,這一過程所涉及到的負載是很大的,因而棄用了該方法,使用forceMerge代替
249      * @see 使用IndexWriter.forceMergeDeletes()方法能夠強制清空回收站中的內容
250      * @see 另外IndexWriter.forceMerge(3)方法會將索引合併爲3段,這3段中的被刪除的數據也會被清空
251      * @see 但其在Lucene3.5以後不建議使用,由於其會消耗大量的開銷,而Lucene會根據狀況自動處理的
252      * @see -----------------------------------------------------------------------------------------------------
253      */
254     public void deleteIndex() {
255         IndexWriter writer = null;
256         try {
257             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
258             // 其參數能夠傳Query或Term....Query指的是能夠查詢出一系列的結果並將其所有刪掉,而Term屬於精確查找
259             writer.deleteDocuments(new Term("id", "1")); // 刪除索引文檔中id爲1的文檔
260         }
261         catch (Exception e) {
262             e.printStackTrace();
263         }
264         finally {
265             if (null != writer) {
266                 try {
267                     writer.close();
268                 }
269                 catch (IOException ce) {
270                     ce.printStackTrace();
271                 }
272             }
273         }
274     }
275 
276     /**
277      * 恢復索引
278      * 
279      * @see 建議棄用
280      */
281     @Deprecated
282     public void unDeleteIndex() {
283         IndexReader reader = null;
284         try {
285             // IndexReader.open(directory)此時該IndexReader默認的readOnly=true,而在恢復索引時應該指定其爲非只讀的
286             reader = IndexReader.open(directory, false);
287             // Deprecated. Write support will be removed in Lucene 4.0. There will be no replacement for this method.
288             reader.undeleteAll();
289         }
290         catch (Exception e) {
291             e.printStackTrace();
292         }
293         finally {
294             if (null != reader) {
295                 try {
296                     reader.close();
297                 }
298                 catch (IOException e) {
299                     e.printStackTrace();
300                 }
301             }
302         }
303     }
304 }
View Code

  3.簡述Lucene中常見的搜索功能

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.text.SimpleDateFormat;
  4 import java.util.Date;
  5 
  6 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  7 import org.apache.lucene.document.Document;
  8 import org.apache.lucene.document.Field;
  9 import org.apache.lucene.document.NumericField;
 10 import org.apache.lucene.index.IndexReader;
 11 import org.apache.lucene.index.IndexWriter;
 12 import org.apache.lucene.index.IndexWriterConfig;
 13 import org.apache.lucene.index.Term;
 14 import org.apache.lucene.queryParser.ParseException;
 15 import org.apache.lucene.queryParser.QueryParser;
 16 import org.apache.lucene.search.BooleanClause.Occur;
 17 import org.apache.lucene.search.BooleanQuery;
 18 import org.apache.lucene.search.FuzzyQuery;
 19 import org.apache.lucene.search.IndexSearcher;
 20 import org.apache.lucene.search.NumericRangeQuery;
 21 import org.apache.lucene.search.PhraseQuery;
 22 import org.apache.lucene.search.PrefixQuery;
 23 import org.apache.lucene.search.Query;
 24 import org.apache.lucene.search.ScoreDoc;
 25 import org.apache.lucene.search.TermQuery;
 26 import org.apache.lucene.search.TermRangeQuery;
 27 import org.apache.lucene.search.TopDocs;
 28 import org.apache.lucene.search.WildcardQuery;
 29 import org.apache.lucene.store.Directory;
 30 import org.apache.lucene.store.FSDirectory;
 31 import org.apache.lucene.util.Version;
 32 
 33 /**
 34  * 【Lucene3.6.2入門系列】第03節_簡述Lucene中常見的搜索功能
 35  * 
 36  * @create Aug 1, 2013 3:54:27 PM
 37  * @author 玄玉<http://blog.csdn.net/jadyer>
 38  */
 39 public class Lucene_03_HelloSearch {
 40     private Directory directory;
 41     private IndexReader reader;
 42     private String[] ids = { "1", "2", "3", "4", "5", "6" };
 43     private String[] names = { "Michael", "Scofield", "Tbag", "Jack", "Jade", "Jadyer" };
 44     private String[] emails = { "aa@jadyer.us", "bb@jadyer.cn", "cc@jadyer.cc", "dd@jadyer.tw", "ee@jadyer.hk", "ff@jadyer.me" };
 45     private String[] contents = { "my java blog is http://blog.csdn.net/jadyer", "my website is http://www.jadyer.cn", "my name is jadyer", "I am JavaDeveloper", "I am from Haerbin", "I like Lucene" };
 46     private int[] attachs = { 9, 3, 5, 4, 1, 2 };
 47     private Date[] dates = new Date[ids.length];
 48 
 49     static Lucene_03_HelloSearch instance = new Lucene_03_HelloSearch();
 50 
 51     public static void main(String[] args) {
 52         instance.searchByTerm("content", "my");
 53         instance.searchByTermRange("name", "M", "o");// 範圍,M~O
 54         instance.searchByNumericRange("attach", 2, 5);
 55         instance.searchByPrefix("content", "b");
 56         instance.searchByWildcard("name", "Ja??er");
 57         instance.searchByFuzzy("name", "Jadk");
 58         instance.searchByPhrase();
 59         instance.searchByQueryParse();
 60         instance.searchPage();
 61         instance.searchPageByAfter();
 62     }
 63 
 64     public void searchPage() {
 65         for (File file : new File("E:/lucene_test/01_index/").listFiles()) {
 66             file.delete();
 67         }
 68         instance = new Lucene_03_HelloSearch(true);
 69         instance.searchPage("mycontent:javase", 2, 10);
 70     }
 71 
 72     public void searchPageByAfter() {
 73         for (File file : new File("E:/lucene_test/01_index/").listFiles()) {
 74             file.delete();
 75         }
 76         instance = new Lucene_03_HelloSearch(true);
 77         instance.searchPageByAfter("mycontent:javase", 3, 10);
 78     }
 79 
 80     public Lucene_03_HelloSearch() {
 81         IndexWriter writer = null;
 82         Document doc = null;
 83         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
 84         try {
 85             dates[0] = sdf.parse("20120601");
 86             dates[1] = sdf.parse("20120603");
 87             dates[2] = sdf.parse("20120605");
 88             dates[3] = sdf.parse("20120607");
 89             dates[4] = sdf.parse("20120609");
 90             dates[5] = sdf.parse("20120611");
 91             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 92             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 93             writer.deleteAll();              // 建立索引以前,先把文檔清空掉
 94             for (int i = 0; i < ids.length; i++) { // 遍歷ID來建立文檔
 95                 doc = new Document();
 96                 doc.add(new Field("id", ids[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
 97                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
 98                 doc.add(new Field("email", emails[i], Field.Store.YES, Field.Index.NOT_ANALYZED));
 99                 doc.add(new Field("email", "test" + i + "" + i + "@jadyer.com", Field.Store.YES, Field.Index.NOT_ANALYZED));
100                 doc.add(new Field("content", contents[i], Field.Store.YES, Field.Index.ANALYZED));
101                 doc.add(new NumericField("attach", Field.Store.YES, true).setIntValue(attachs[i]));        // 爲數字加索引(第三個參數指定是否索引)
102                 doc.add(new NumericField("attach", Field.Store.YES, true).setIntValue((i + 1) * 100));         // 假設有多個附件
103                 doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[i].getTime())); // 爲日期加索引
104                 writer.addDocument(doc);
105             }
106         }
107         catch (Exception e) {
108             e.printStackTrace();
109         }
110         finally {
111             if (null != writer) {
112                 try {
113                     writer.close();
114                 }
115                 catch (IOException ce) {
116                     ce.printStackTrace();
117                 }
118             }
119         }
120     }
121 
122     /**
123      * 針對分頁搜索建立索引
124      */
125     public Lucene_03_HelloSearch(boolean pageFlag) {
126         String[] myNames = new String[50];
127         String[] myContents = new String[50];
128         for (int i = 0; i < 50; i++) {
129             myNames[i] = "file(" + i + ")";
130             myContents[i] = "I love JavaSE, also love Lucene(" + i + ")";
131         }
132         IndexWriter writer = null;
133         Document doc = null;
134         try {
135             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
136             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
137             writer.deleteAll();
138             for (int i = 0; i < myNames.length; i++) {
139                 doc = new Document();
140                 doc.add(new Field("myname", myNames[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
141                 doc.add(new Field("mycontent", myContents[i], Field.Store.YES, Field.Index.ANALYZED));
142                 writer.addDocument(doc);
143             }
144         }
145         catch (IOException e) {
146             e.printStackTrace();
147         }
148         finally {
149             if (null != writer) {
150                 try {
151                     writer.close();
152                 }
153                 catch (IOException ce) {
154                     ce.printStackTrace();
155                 }
156             }
157         }
158     }
159 
160     /**
161      * 獲取IndexSearcher實例
162      */
163     private IndexSearcher getIndexSearcher() {
164         try {
165             if (reader == null) {
166                 reader = IndexReader.open(directory);
167             }
168             else {
169                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
170                 // 若是當前reader在打開期間index發生改變,則打開並返回一個新的IndexReader,不然返回null
171                 IndexReader ir = IndexReader.openIfChanged(reader);
172                 if (ir != null) {
173                     reader.close(); // 關閉原reader
174                     reader = ir;    // 賦予新reader
175                 }
176             }
177             return new IndexSearcher(reader);
178         }
179         catch (Exception e) {
180             e.printStackTrace();
181         }
182         return null; // 發生異常則返回null
183     }
184 
185     /**
186      * 執行搜索操做
187      * 
188      * @param query
189      *            (搜索的Query對象)
190      */
191     private void doSearch(Query query) {
192         IndexSearcher searcher = this.getIndexSearcher();
193         try {
194             // 第二個參數指定搜索後顯示的最多的記錄數,其與tds.totalHits沒有聯繫
195             TopDocs tds = searcher.search(query, 10);
196             System.out.println("本次搜索到[" + tds.totalHits + "]條記錄");
197             for (ScoreDoc sd : tds.scoreDocs) {
198                 Document doc = searcher.doc(sd.doc);
199                 System.out.println("content =" + doc.get("content") + "  ");
200                 System.out.print("文檔編號=" + sd.doc + "  文檔權值=" + doc.getBoost() + "  文檔評分=" + sd.score + "    ");
201                 System.out.print("id=" + doc.get("id") + "  email=" + doc.get("email") + "  name=" + doc.get("name") + "  ");
202                 // 獲取多個同名域的方式
203                 String[] attachValues = doc.getValues("attach");
204                 for (String attach : attachValues) {
205                     System.out.print("attach=" + attach + "  ");
206                 }
207                 System.out.println();
208             }
209         }
210         catch (IOException e) {
211             e.printStackTrace();
212         }
213         finally {
214             if (null != searcher) {
215                 try {
216                     searcher.close(); // 記得關閉IndexSearcher
217                 }
218                 catch (IOException e) {
219                     e.printStackTrace();
220                 }
221             }
222         }
223     }
224 
225     /**
226      * 精確匹配搜索
227      * 
228      * @param fieldName
229      *            域名(至關於表的字段名)
230      * @param keyWords
231      *            搜索的關鍵字
232      */
233     public void searchByTerm(String fieldName, String keyWords) {
234         Query query = new TermQuery(new Term(fieldName, keyWords));
235         this.doSearch(query);
236     }
237 
238     /**
239      * 基於範圍的搜索
240      * 
241      * @param fieldName
242      *            域名(至關於表的字段名)
243      * @param start
244      *            開始字符
245      * @param end
246      *            結束字符
247      */
248     public void searchByTermRange(String fieldName, String start, String end) {
249         Query query = new TermRangeQuery(fieldName, start, end, true, true); // 後面兩個參數用於指定開區間或閉區間
250         this.doSearch(query);
251     }
252 
253     /**
254      * 針對數字的搜索
255      */
256     public void searchByNumericRange(String fieldName, int min, int max) {
257         Query query = NumericRangeQuery.newIntRange(fieldName, min, max, true, true);
258         this.doSearch(query);
259     }
260 
261     /**
262      * 基於前綴的搜索
263      * 
264      * @see 它是對Field分詞後的結果進行前綴查找的結果
265      */
266     public void searchByPrefix(String fieldName, String prefix) {
267         Query query = new PrefixQuery(new Term(fieldName, prefix));
268         this.doSearch(query);
269     }
270 
271     /**
272      * 基於通配符的搜索
273      * 
274      * @see *-->任意多個字符
275      * @see ?-->一個字符
276      */
277     public void searchByWildcard(String fieldName, String wildcard) {
278         Query query = new WildcardQuery(new Term(fieldName, wildcard));
279         this.doSearch(query);
280     }
281 
282     /**
283      * 模糊搜索
284      * 
285      * @see 與通配符搜索不一樣
286      */
287     public void searchByFuzzy(String fieldName, String fuzzy) {
288         Query query = new FuzzyQuery(new Term(fieldName, fuzzy));
289         this.doSearch(query);
290     }
291 
292     /**
293      * 多條件搜索
294      * 
295      * @see 本例中搜索name值中以Ja開頭,且content中包含am的內容
296      * @see Occur.MUST------表示此條件必須爲true
297      * @see Occur.MUST_NOT--表示此條件必須爲false
298      * @see Occur.SHOULD----表示此條件非必須
299      */
300     public void searchByBoolean() {
301         BooleanQuery query = new BooleanQuery();
302         query.add(new WildcardQuery(new Term("name", "Ja*")), Occur.MUST);
303         query.add(new TermQuery(new Term("content", "am")), Occur.MUST);
304         this.doSearch(query);
305     }
306 
307     /**
308      * 短語搜索
309      * 
310      * @see 很遺憾的是短語查詢對中文搜索沒有太大的做用,但對英文搜索是很好用的,但它的開銷比較大,儘可能少用
311      */
312     public void searchByPhrase() {
313         PhraseQuery query = new PhraseQuery();
314         query.setSlop(1);                          // 設置跳數
315         query.add(new Term("content", "am"));      // 第一個Term
316         query.add(new Term("content", "Haerbin")); // 產生距離以後的第二個Term
317         this.doSearch(query);
318     }
319 
320     /**
321      * 基於QueryParser的搜索
322      */
323     public void searchByQueryParse() {
324         QueryParser parser = new QueryParser(Version.LUCENE_36, "content", new StandardAnalyzer(Version.LUCENE_36));
325         Query query = null;
326         try {
327             // query = parser.parse("Haerbin"); //搜索content中包含[Haerbin]的記錄
328             // query = parser.parse("I AND Haerbin"); //搜索content中包含[I]和[Haerbin]的記錄
329             // query = parser.parse("Lucene OR Haerbin"); //搜索content中包含[Lucene]或者[Haerbin]的記錄
330             // query = parser.parse("Lucene Haerbin"); //搜索content中包含[Lucene]或者[Haerbin]的記錄
331             // parser.setDefaultOperator(Operator.AND); //將空格的默認操做OR修改成AND
332             // //1)若是name域在索引時,不進行分詞,那麼不管這裏寫成[name:Jadyer]仍是[name:jadyer],最後獲得的都是0條記錄
333             // //2)因爲name原值爲大寫[J],若索引時不對name分詞,除非修改name原值爲小寫[j],而且搜索[name:jadyer]才能獲得記錄
334             // query = parser.parse("name:Jadyer"); //修改搜索域爲name=Jadyer的記錄
335             // query = parser.parse("name:Ja*"); //支持通配符
336             // query = parser.parse("\"I am\""); //搜索content中包含[I am]的記錄(注意不能使用parse("content:'I am'"))
337             // parser.setAllowLeadingWildcard(true); //設置容許[*]或[?]出如今查詢字符的第一位,即[name:*de],不然[name:*de]會報異常
338             // query = parser.parse("name:*de"); //Lucene默認的第一個字符不容許爲通配符,由於這樣效率比較低
339             // //parse("+am +name:Jade")--------------搜索content中包括[am]的,而且name=Jade的記錄
340             // //parse("am AND NOT name:Jade")--------搜索content中包括[am]的,而且nam不是Jade的記錄
341             // //parse("(blog OR am) AND name:Jade")--搜索content中包括[blog]或者[am]的,而且name=Jade的記錄
342             // query = parser.parse("-name:Jack +I"); //搜索content中包括[I]的,而且name不是Jack的記錄(加減號要放到域說明的前面)
343             // query = parser.parse("id:[1 TO 3]"); //搜索id值從1到3的記錄(TO必須大寫,且這種方式沒有辦法匹配數字)
344             // query = parser.parse("id:{1 TO 3}"); //搜索id=2的記錄
345             query = parser.parse("name:Jadk~");        // 模糊搜索
346         }
347         catch (ParseException e) {
348             e.printStackTrace();
349         }
350         this.doSearch(query);
351     }
352 
353     /**
354      * 普通的分頁搜索
355      * 
356      * @see 適用於lucene3.5以前
357      * @param expr
358      *            搜索表達式
359      * @param pageIndex
360      *            頁碼
361      * @param pageSize
362      *            分頁大小
363      */
364     public void searchPage(String expr, int pageIndex, int pageSize) {
365         IndexSearcher searcher = this.getIndexSearcher();
366         QueryParser parser = new QueryParser(Version.LUCENE_36, "mycontent", new StandardAnalyzer(Version.LUCENE_36));
367         try {
368             Query query = parser.parse(expr);
369             TopDocs tds = searcher.search(query, pageIndex * pageSize);
370             ScoreDoc[] sds = tds.scoreDocs;
371             for (int i = (pageIndex - 1) * pageSize; i < pageIndex * pageSize; i++) {
372                 Document doc = searcher.doc(sds[i].doc);
373                 System.out.println("文檔編號:" + sds[i].doc + "-->" + doc.get("myname") + "-->" + doc.get("mycontent"));
374             }
375         }
376         catch (Exception e) {
377             e.printStackTrace();
378         }
379         finally {
380             if (null != searcher) {
381                 try {
382                     searcher.close();
383                 }
384                 catch (IOException e) {
385                     e.printStackTrace();
386                 }
387             }
388         }
389     }
390 
391     /**
392      * 基於searchAfter的分頁搜索
393      * 
394      * @see 適用於Lucene3.5
395      * @param expr
396      *            搜索表達式
397      * @param pageIndex
398      *            頁碼
399      * @param pageSize
400      *            分頁大小
401      */
402     public void searchPageByAfter(String expr, int pageIndex, int pageSize) {
403         IndexSearcher searcher = this.getIndexSearcher();
404         QueryParser parser = new QueryParser(Version.LUCENE_36, "mycontent", new StandardAnalyzer(Version.LUCENE_36));
405         try {
406             Query query = parser.parse(expr);
407             TopDocs tds = searcher.search(query, (pageIndex - 1) * pageSize);
408             // 使用IndexSearcher.searchAfter()搜索,該方法第一個參數爲上一頁記錄中的最後一條記錄
409             if (pageIndex > 1) {
410                 tds = searcher.searchAfter(tds.scoreDocs[(pageIndex - 1) * pageSize - 1], query, pageSize);
411             }
412             else {
413                 tds = searcher.searchAfter(null, query, pageSize);
414             }
415             for (ScoreDoc sd : tds.scoreDocs) {
416                 Document doc = searcher.doc(sd.doc);
417                 System.out.println("文檔編號:" + sd.doc + "-->" + doc.get("myname") + "-->" + doc.get("mycontent"));
418             }
419         }
420         catch (Exception e) {
421             e.printStackTrace();
422         }
423         finally {
424             if (null != searcher) {
425                 try {
426                     searcher.close();
427                 }
428                 catch (IOException e) {
429                     e.printStackTrace();
430                 }
431             }
432         }
433     }
434 }
View Code

  4.中文分詞器

  1 import java.io.IOException;
  2 import java.io.StringReader;
  3 
  4 import org.apache.lucene.analysis.Analyzer;
  5 import org.apache.lucene.analysis.SimpleAnalyzer;
  6 import org.apache.lucene.analysis.StopAnalyzer;
  7 import org.apache.lucene.analysis.TokenStream;
  8 import org.apache.lucene.analysis.WhitespaceAnalyzer;
  9 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 10 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 11 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 12 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 13 import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
 14 import org.apache.lucene.util.Version;
 15 
 16 import com.chenlb.mmseg4j.analysis.ComplexAnalyzer;
 17 import com.chenlb.mmseg4j.analysis.MMSegAnalyzer;
 18 
 19 /**
 20  * 【Lucene3.6.2入門系列】第04節_中文分詞器
 21  * 
 22  * @see -----------------------------------------------------------------------------------------------------------------------
 23  * @see Lucene3.5推薦的四大分詞器:SimpleAnalyzer,StopAnalyzer,WhitespaceAnalyzer,StandardAnalyzer
 24  * @see 這四大分詞器有一個共同的抽象父類,此類有個方法public final TokenStream tokenStream(),即分詞的一個流
 25  * @see 假設有這樣的文本"how are you thank you",實際它是以一個java.io.Reader傳進分詞器中
 26  * @see Lucene分詞器處理完畢後,會把整個分詞轉換爲TokenStream,這個TokenStream中就保存全部的分詞信息
 27  * @see TokenStream有兩個實現類,分別爲Tokenizer和TokenFilter
 28  * @see Tokenizer---->用於將一組數據劃分爲獨立的語彙單元(即一個一個的單詞)
 29  * @see TokenFilter-->過濾語彙單元
 30  * @see -----------------------------------------------------------------------------------------------------------------------
 31  * @see 分詞流程
 32  * @see 1)將一組數據流java.io.Reader交給Tokenizer,由其將數據轉換爲一個個的語彙單元
 33  * @see 2)經過大量的TokenFilter對已經分好詞的數據進行過濾操做,最後產生TokenStream
 34  * @see 3)經過TokenStream完成索引的存儲
 35  * @see -----------------------------------------------------------------------------------------------------------------------
 36  * @see Tokenizer的一些子類
 37  * @see KeywordTokenizer-----不分詞,傳什麼就索引什麼
 38  * @see StandardTokenizer----標準分詞,它有一些較智能的分詞操做,諸如將'jadyer@yeah.net'中的'yeah.net'看成一個分詞流
 39  * @see CharTokenizer--------針對字符進行控制的,它還有兩個子類WhitespaceTokenizer和LetterTokenizer
 40  * @see WhitespaceTokenizer--使用空格進行分詞,諸如將'Thank you,I am jadyer'會被分爲4個詞
 41  * @see LetterTokenizer------基於文本單詞的分詞,它會根據標點符號來分詞,諸如將'Thank you,I am jadyer'會被分爲5個詞
 42  * @see LowerCaseTokenizer---它是LetterTokenizer的子類,它會將數據轉爲小寫並分詞
 43  * @see -----------------------------------------------------------------------------------------------------------------------
 44  * @see TokenFilter的一些子類
 45  * @see StopFilter--------它會停用一些語彙單元
 46  * @see LowerCaseFilter---將數據轉換爲小寫
 47  * @see StandardFilter----對標準輸出流作一些控制
 48  * @see PorterStemFilter--還原一些數據,好比將coming還原爲come,將countries還原爲country
 49  * @see -----------------------------------------------------------------------------------------------------------------------
 50  * @see eg:'how are you thank you'會被分詞爲'how','are','you','thank','you'合計5個語彙單元
 51  * @see 那麼應該保存什麼東西,才能使之後在須要還原數據時保證正確的還原呢???其實主要保存三個東西,以下所示
 52  * @see CharTermAttribute(Lucene3.5之前叫TermAttribute),OffsetAttribute,PositionIncrementAttribute
 53  * @see 1)CharTermAttribute-----------保存相應的詞彙,這裏保存的就是'how','are','you','thank','you'
 54  * @see 2)OffsetAttribute-------------保存各詞彙之間的偏移量(大體理解爲順序),好比'how'的首尾字母偏移量爲0和3,'are'爲4和7,'thank'爲12和17
 55  * @see 3)PositionIncrementAttribute--保存詞與詞之間的位置增量,好比'how'和'are'增量爲1,'are'和'you'之間的也是1,'you'和'thank'的也是1
 56  * @see 但假設'are'是停用詞(StopFilter的效果),那麼'how'和'you'之間的位置增量就變成了2
 57  * @see 當咱們查找某一個元素時,Lucene會先經過位置增量來取這個元素,但若是兩個詞的位置增量相同,會發生什麼狀況呢
 58  * @see 假設還有一個單詞'this',它的位置增量和'how'是相同的,那麼當咱們在界面中搜索'this'時
 59  * @see 也會搜到'how are you thank you',這樣就能夠有效的作同義詞了,目前很是流行的一個叫作WordNet的東西,就能夠作同義詞的搜索
 60  * @see -----------------------------------------------------------------------------------------------------------------------
 61  * @see 中文分詞器
 62  * @see Lucene默認提供的衆多分詞器徹底不適用中文
 63  * @see 1)Paoding--庖丁解牛分詞器,官網爲http://code.google.com/p/paoding(貌似已託管在http://git.oschina.net/zhzhenqin/paoding-analysis)
 64  * @see 2)MMSeg4j--聽說它使用的是搜狗的詞庫,官網爲https://code.google.com/p/mmseg4j(另外還有一個https://code.google.com/p/jcseg)
 65  * @ses 3)IK-------https://code.google.com/p/ik-analyzer/
 66  * @see -----------------------------------------------------------------------------------------------------------------------
 67  * @see MMSeg4j的使用
 68  * @see 1)下載mmseg4j-1.8.5.zip並引入mmseg4j-all-1.8.5-with-dic.jar
 69  * @see 2)在須要指定分詞器的位置編寫new MMSegAnalyzer()便可
 70  * @see 注1)因爲使用的mmseg4j-all-1.8.5-with-dic.jar中已自帶了詞典,故直接new MMSegAnalyzer()便可
 71  * @see 注2)若引入的是mmseg4j-all-1.8.5.jar,則應指明詞典目錄,如new MMSegAnalyzer("D:\\Develop\\mmseg4j-1.8.5\\data")
 72  * @see 但若非要使用new MMSegAnalyzer(),則要將mmseg4j-1.8.5.zip自帶的data目錄拷入classpath下便可
 73  * @see 總結:直接引入mmseg4j-all-1.8.5-with-dic.jar就好了
 74  * @see -----------------------------------------------------------------------------------------------------------------------
 75  * @create Aug 2, 2013 5:30:45 PM
 76  * @author 玄玉<http://blog.csdn.net/jadyer>
 77  */
 78 public class Lucene_04_HelloChineseAnalyzer {
 79     /**
 80      * 查看分詞信息
 81      * 
 82      * @see TokenStream還有兩個屬性,分別爲FlagsAttribute和PayloadAttribute,都是開發時用的
 83      * @see FlagsAttribute----標註位屬性
 84      * @see PayloadAttribute--作負載的屬性,用來檢測是否已超過負載,超過則能夠決定是否中止搜索等等
 85      * @param txt
 86      *            待分詞的字符串
 87      * @param analyzer
 88      *            所使用的分詞器
 89      * @param displayAll
 90      *            是否顯示全部的分詞信息
 91      */
 92     public static void displayTokenInfo(String txt, Analyzer analyzer, boolean displayAll) {
 93         // 第一個參數沒有任何意義,能夠隨便傳一個值,它只是爲了顯示分詞
 94         // 這裏就是使用指定的分詞器將'txt'分詞,分詞後會產生一個TokenStream(可將分詞後的每一個單詞理解爲一個Token)
 95         TokenStream stream = analyzer.tokenStream("此參數無心義", new StringReader(txt));
 96         // 用於查看每個語彙單元的信息,即分詞的每個元素
 97         // 這裏建立的屬性會被添加到TokenStream流中,並隨着TokenStream而增長(此屬性就是用來裝載每一個Token的,即分詞後的每一個單詞)
 98         // 當調用TokenStream.incrementToken()時,就會指向到這個單詞流中的第一個單詞,即此屬性表明的就是分詞後的第一個單詞
 99         // 能夠形象的理解成一隻碗,用來盛放TokenStream中每一個單詞的碗,每調用一次incrementToken()後,這個碗就會盛放流中的下一個單詞
100         CharTermAttribute cta = stream.addAttribute(CharTermAttribute.class);
101         // 用於查看位置增量(指的是語彙單元之間的距離,可理解爲元素與元素之間的空格,即間隔的單元數)
102         PositionIncrementAttribute pia = stream.addAttribute(PositionIncrementAttribute.class);
103         // 用於查看每一個語彙單元的偏移量
104         OffsetAttribute oa = stream.addAttribute(OffsetAttribute.class);
105         // 用於查看使用的分詞器的類型信息
106         TypeAttribute ta = stream.addAttribute(TypeAttribute.class);
107         try {
108             if (displayAll) {
109                 // 等價於while(stream.incrementToken())
110                 for (; stream.incrementToken();) {
111                     System.out.println(ta.type() + " " + pia.getPositionIncrement() + " [" + oa.startOffset() + "-" + oa.endOffset() + "] [" + cta + "]");
112                 }
113             }
114             else {
115                 System.out.println();
116                 while (stream.incrementToken()) {
117                     System.out.print("[" + cta + "]");
118                 }
119             }
120         }
121         catch (IOException e) {
122             e.printStackTrace();
123         }
124     }
125 
126     /**
127      * 測試一下中文分詞的效果
128      */
129     public static void main(String[] args) {
130         String txt = "測試一下中文分詞的效果";
131         // displayTokenInfo(txt, new StandardAnalyzer(Version.LUCENE_36), false);
132         // displayTokenInfo(txt, new StopAnalyzer(Version.LUCENE_36), false);
133         // displayTokenInfo(txt, new SimpleAnalyzer(Version.LUCENE_36), false);
134         // displayTokenInfo(txt, new WhitespaceAnalyzer(Version.LUCENE_36), false);
135         displayTokenInfo(txt, new MMSegAnalyzer(), true);
136         // displayTokenInfo(txt, new SimpleAnalyzer(), false);
137         // displayTokenInfo(txt, new ComplexAnalyzer(), false);
138     }
139 }
View Code

  5.高級搜索之排序

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.text.SimpleDateFormat;
  4 import java.util.Date;
  5 
  6 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  7 import org.apache.lucene.document.Document;
  8 import org.apache.lucene.document.Field;
  9 import org.apache.lucene.document.NumericField;
 10 import org.apache.lucene.index.IndexReader;
 11 import org.apache.lucene.index.IndexWriter;
 12 import org.apache.lucene.index.IndexWriterConfig;
 13 import org.apache.lucene.queryParser.QueryParser;
 14 import org.apache.lucene.search.IndexSearcher;
 15 import org.apache.lucene.search.ScoreDoc;
 16 import org.apache.lucene.search.Sort;
 17 import org.apache.lucene.search.SortField;
 18 import org.apache.lucene.search.TopDocs;
 19 import org.apache.lucene.store.Directory;
 20 import org.apache.lucene.store.FSDirectory;
 21 import org.apache.lucene.util.Version;
 22 
 23 /**
 24  * 【Lucene3.6.2入門系列】第06節_高級搜索之排序
 25  * 
 26  * @create Aug 19, 2013 10:38:19 AM
 27  * @author 玄玉<http://blog.csdn.net/jadyer>
 28  */
 29 public class Lucene_05_AdvancedSearchBySort {
 30     private Directory directory;
 31     private IndexReader reader;
 32 
 33     public Lucene_05_AdvancedSearchBySort() {
 34         /** 文件大小 */
 35         int[] sizes = { 90, 10, 20, 10, 60, 50 };
 36         /** 文件名 */
 37         String[] names = { "Michael.java", "Scofield.ini", "Tbag.txt", "Jack", "Jade", "Jadyer" };
 38         /** 文件內容 */
 39         String[] contents = { "my java blog is http://blog.csdn.net/jadyer", "my Java Website is http://www.jadyer.cn", "my name is jadyer", "I am a Java Developer", "I am from Haerbin", "I like java of Lucene" };
 40         /** 文件日期 */
 41         Date[] dates = new Date[sizes.length];
 42         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
 43         IndexWriter writer = null;
 44         Document doc = null;
 45         try {
 46             dates[0] = sdf.parse("20130407 15:25:30");
 47             dates[1] = sdf.parse("20130407 16:30:45");
 48             dates[2] = sdf.parse("20130213 11:15:25");
 49             dates[3] = sdf.parse("20130808 09:30:55");
 50             dates[4] = sdf.parse("20130526 13:54:22");
 51             dates[5] = sdf.parse("20130701 17:35:34");
 52             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 53             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 54             writer.deleteAll();
 55             for (int i = 0; i < sizes.length; i++) {
 56                 doc = new Document();
 57                 doc.add(new NumericField("size", Field.Store.YES, true).setIntValue(sizes[i]));
 58                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
 59                 doc.add(new Field("content", contents[i], Field.Store.YES, Field.Index.ANALYZED));
 60                 doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[i].getTime()));
 61                 writer.addDocument(doc);
 62             }
 63         }
 64         catch (Exception e) {
 65             e.printStackTrace();
 66         }
 67         finally {
 68             if (null != writer) {
 69                 try {
 70                     writer.close();
 71                 }
 72                 catch (IOException ce) {
 73                     ce.printStackTrace();
 74                 }
 75             }
 76         }
 77     }
 78 
 79     /**
 80      * 獲取IndexReader實例
 81      */
 82     private IndexReader getIndexReader() {
 83         try {
 84             if (reader == null) {
 85                 reader = IndexReader.open(directory);
 86             }
 87             else {
 88                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
 89                 // 若是當前reader在打開期間index發生改變,則打開並返回一個新的IndexReader,不然返回null
 90                 IndexReader ir = IndexReader.openIfChanged(reader);
 91                 if (ir != null) {
 92                     reader.close(); // 關閉原reader
 93                     reader = ir;    // 賦予新reader
 94                 }
 95             }
 96             return reader;
 97         }
 98         catch (Exception e) {
 99             e.printStackTrace();
100         }
101         return null; // 發生異常則返回null
102     }
103 
104     /**
105      * 搜索排序
106      * 
107      * @see 關於Sort參數的可輸入規則,以下所示
108      * @see 1)Sort.INDEXORDER--使用文檔編號從小到大的順序進行排序
109      * @see 2)Sort.RELEVANCE---使用文檔評分從大到小的順序進行排序,也是默認的排序規則,等價於search(query, 10)
110      * @see 3)new Sort(new SortField("size", SortField.INT))-----------使用文件大小從小到大的順序排序
111      * @see 4)new Sort(new SortField("date", SortField.LONG))----------使用文件日期從之前到如今的順序排序
112      * @see 5)new Sort(new SortField("name", SortField.STRING))--------使用文件名從A到Z的順序排序
113      * @see 6)new Sort(new SortField("name", SortField.STRING, true))--使用文件名從Z到A的順序排序
114      * @see 7)new Sort(new SortField("size", SortField.INT), SortField.FIELD_SCORE)--先按照文件大小排序,再按照文檔評分排序(能夠指定多個排序規則)
115      * @see 注意:以上7個Sort再打印文檔評分時都是NaN,只有search(query, 10)纔會正確打印文檔評分
116      * @param expr
117      *            搜索表達式
118      * @param sort
119      *            排序規則
120      */
121     public void searchBySort(String expr, Sort sort) {
122         IndexSearcher searcher = new IndexSearcher(this.getIndexReader());
123         QueryParser parser = new QueryParser(Version.LUCENE_36, "content", new StandardAnalyzer(Version.LUCENE_36));
124         TopDocs tds = null;
125         try {
126             if (null == sort) {
127                 tds = searcher.search(parser.parse(expr), 10);
128             }
129             else {
130                 tds = searcher.search(parser.parse(expr), 10, sort);
131             }
132             for (ScoreDoc sd : tds.scoreDocs) {
133                 Document doc = searcher.doc(sd.doc);
134                 System.out.println("content=" + doc.get("content"));
135                 System.out.print("文檔編號=" + sd.doc + "  文檔權值=" + doc.getBoost() + "  文檔評分=" + sd.score + "    ");
136                 System.out.println("size=" + doc.get("size") + "  date=" + new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date(Long.parseLong(doc.get("date")))) + "  name=" + doc.get("name"));
137             }
138         }
139         catch (Exception e) {
140             e.printStackTrace();
141         }
142         finally {
143             if (searcher != null) {
144                 try {
145                     searcher.close();
146                 }
147                 catch (IOException e) {
148                     e.printStackTrace();
149                 }
150             }
151         }
152     }
153 
154     /**
155      * 測試一下排序效果
156      */
157     public static void main(String[] args) {
158         Lucene_05_AdvancedSearchBySort advancedSearch = new Lucene_05_AdvancedSearchBySort();
159         // //使用文檔評分從大到小的順序進行排序,也是默認的排序規則
160         // advancedSearch.searchBySort("Java", null);
161         // advancedSearch.searchBySort("Java", Sort.RELEVANCE);
162         // //使用文檔編號從小到大的順序進行排序
163         // advancedSearch.searchBySort("Java", Sort.INDEXORDER);
164         // //使用文件大小從小到大的順序排序
165         // advancedSearch.searchBySort("Java", new Sort(new SortField("size", SortField.INT)));
166         // //使用文件日期從之前到如今的順序排序
167         // advancedSearch.searchBySort("Java", new Sort(new SortField("date", SortField.LONG)));
168         // //使用文件名從A到Z的順序排序
169         // advancedSearch.searchBySort("Java", new Sort(new SortField("name", SortField.STRING)));
170         // //使用文件名從Z到A的順序排序
171         // advancedSearch.searchBySort("Java", new Sort(new SortField("name", SortField.STRING, true)));
172         // 先按照文件大小排序,再按照文檔評分排序(能夠指定多個排序規則)
173         advancedSearch.searchBySort("Java", new Sort(new SortField("size", SortField.INT), SortField.FIELD_SCORE));
174     }
175 }
View Code

  6.高級搜索之普通Filter和自定義Filter

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.text.ParseException;
  4 import java.text.SimpleDateFormat;
  5 import java.util.Date;
  6 
  7 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  8 import org.apache.lucene.document.Document;
  9 import org.apache.lucene.document.Field;
 10 import org.apache.lucene.document.NumericField;
 11 import org.apache.lucene.index.IndexReader;
 12 import org.apache.lucene.index.IndexWriter;
 13 import org.apache.lucene.index.IndexWriterConfig;
 14 import org.apache.lucene.index.Term;
 15 import org.apache.lucene.index.TermDocs;
 16 import org.apache.lucene.queryParser.QueryParser;
 17 import org.apache.lucene.search.DocIdSet;
 18 import org.apache.lucene.search.Filter;
 19 import org.apache.lucene.search.IndexSearcher;
 20 import org.apache.lucene.search.NumericRangeFilter;
 21 import org.apache.lucene.search.ScoreDoc;
 22 import org.apache.lucene.search.TopDocs;
 23 import org.apache.lucene.store.Directory;
 24 import org.apache.lucene.store.FSDirectory;
 25 import org.apache.lucene.util.OpenBitSet;
 26 import org.apache.lucene.util.Version;
 27 
 28 /**
 29  * 【Lucene3.6.2入門系列】第07節_高級搜索之普通Filter和自定義Filter
 30  * 
 31  * @create Aug 19, 2013 11:13:40 AM
 32  * @author 玄玉<http://blog.csdn.net/jadyer>
 33  */
 34 public class Lucene_06_AdvancedSearchByFilter {
 35     private Directory directory;
 36     private IndexReader reader;
 37 
 38     /**
 39      * 測試一下過濾效果
 40      */
 41     public static void main(String[] args) throws ParseException {
 42         Lucene_06_AdvancedSearchByFilter advancedSearch = new Lucene_06_AdvancedSearchByFilter();
 43         // //過濾文件名首字母從'h'到'n'的記錄(注意hn要小寫)
 44         // advancedSearch.searchByFilter("Java", new TermRangeFilter("name", "h", "n", true, true));
 45         // //過濾文件大小在30到80之內的記錄
 46         // advancedSearch.searchByFilter("Java", NumericRangeFilter.newIntRange("size", 30, 80, true, true));
 47         // //過濾文件日期在20130701 00:00:00到20130808 23:59:59之間的記錄
 48         // Long min = Long.valueOf(new SimpleDateFormat("yyyyMMdd").parse("20130701").getTime());
 49         // Long max = Long.valueOf(new SimpleDateFormat("yyyyMMdd HH:mm:ss").parse("20130808 23:59:59").getTime());
 50         // advancedSearch.searchByFilter("Java", NumericRangeFilter.newLongRange("date", min, max, true, true));
 51         // //過濾文件名以'ja'打頭的(注意ja要小寫)
 52         // advancedSearch.searchByFilter("Java", new QueryWrapperFilter(new WildcardQuery(new Term("name", "ja*"))));
 53         // 自定義Filter
 54         advancedSearch.searchByFilter("Java", advancedSearch.new MyFilter());
 55     }
 56 
 57     public Lucene_06_AdvancedSearchByFilter() {
 58         /** 文件大小 */
 59         int[] sizes = { 90, 10, 20, 10, 60, 50 };
 60         /** 文件名 */
 61         String[] names = { "Michael.java", "Scofield.ini", "Tbag.txt", "Jack", "Jade", "Jadyer" };
 62         /** 文件內容 */
 63         String[] contents = { "my java blog is http://blog.csdn.net/jadyer", "my Java Website is http://www.jadyer.cn", "my name is jadyer", "I am a Java Developer", "I am from Haerbin", "I like java of Lucene" };
 64         /** 文件日期 */
 65         Date[] dates = new Date[sizes.length];
 66         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
 67         IndexWriter writer = null;
 68         Document doc = null;
 69         try {
 70             dates[0] = sdf.parse("20130407 15:25:30");
 71             dates[1] = sdf.parse("20130407 16:30:45");
 72             dates[2] = sdf.parse("20130213 11:15:25");
 73             dates[3] = sdf.parse("20130808 09:30:55");
 74             dates[4] = sdf.parse("20130526 13:54:22");
 75             dates[5] = sdf.parse("20130701 17:35:34");
 76             directory = FSDirectory.open(new File("myExample/01_index/"));
 77             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 78             writer.deleteAll();
 79             for (int i = 0; i < sizes.length; i++) {
 80                 doc = new Document();
 81                 doc.add(new NumericField("size", Field.Store.YES, true).setIntValue(sizes[i]));
 82                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
 83                 doc.add(new Field("content", contents[i], Field.Store.NO, Field.Index.ANALYZED));
 84                 doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[i].getTime()));
 85                 // 爲每一個文檔添加一個fileID(與ScoreDoc.doc不一樣),專門在自定義Filter時使用
 86                 doc.add(new Field("fileID", String.valueOf(i), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
 87                 writer.addDocument(doc);
 88             }
 89         }
 90         catch (Exception e) {
 91             e.printStackTrace();
 92         }
 93         finally {
 94             if (null != writer) {
 95                 try {
 96                     writer.close();
 97                 }
 98                 catch (IOException ce) {
 99                     ce.printStackTrace();
100                 }
101             }
102         }
103     }
104 
105     /**
106      * 獲取IndexReader實例
107      */
108     private IndexReader getIndexReader() {
109         try {
110             if (reader == null) {
111                 reader = IndexReader.open(directory);
112             }
113             else {
114                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
115                 // 若是當前reader在打開期間index發生改變,則打開並返回一個新的IndexReader,不然返回null
116                 IndexReader ir = IndexReader.openIfChanged(reader);
117                 if (ir != null) {
118                     reader.close(); // 關閉原reader
119                     reader = ir;    // 賦予新reader
120                 }
121             }
122             return reader;
123         }
124         catch (Exception e) {
125             e.printStackTrace();
126         }
127         return null; // 發生異常則返回null
128     }
129 
130     /**
131      * 搜索過濾
132      */
133     public void searchByFilter(String expr, Filter filter) {
134         IndexSearcher searcher = new IndexSearcher(this.getIndexReader());
135         QueryParser parser = new QueryParser(Version.LUCENE_36, "content", new StandardAnalyzer(Version.LUCENE_36));
136         TopDocs tds = null;
137         try {
138             if (null == filter) {
139                 tds = searcher.search(parser.parse(expr), 10);
140             }
141             else {
142                 tds = searcher.search(parser.parse(expr), filter, 10);
143             }
144             for (ScoreDoc sd : tds.scoreDocs) {
145                 Document doc = searcher.doc(sd.doc);
146                 System.out.print("文檔編號=" + sd.doc + "  文檔權值=" + doc.getBoost() + "  文檔評分=" + sd.score + "    ");
147                 System.out.println("fileID=" + doc.get("fileID") + "  size=" + doc.get("size") + "  date=" + new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date(Long.parseLong(doc.get("date")))) + "  name=" + doc.get("name"));
148             }
149         }
150         catch (Exception e) {
151             e.printStackTrace();
152         }
153         finally {
154             if (searcher != null) {
155                 try {
156                     searcher.close();
157                 }
158                 catch (IOException e) {
159                     e.printStackTrace();
160                 }
161             }
162         }
163     }
164 
165     /**
166      * 自定義Filter
167      * 
168      * @see ------------------------------------------------------------------------------------------
169      * @see 本例的應用場景
170      * @see 假設不少的數據,而後刪除了其中的某幾條數據,此時在接受搜索請求時爲保證不會搜索到已刪除的數據
171      * @see 那麼能夠更新索引,但更新索引會消耗不少時間(由於數據量大),而又要保證已刪除的數據不會被搜索到
172      * @see 此時就能夠自定義Filter,原理即搜索過程當中,當發現此記錄爲已刪除記錄,則不添加到返回的搜索結果集中
173      * @see ------------------------------------------------------------------------------------------
174      * @see 自定義Filter步驟以下
175      * @see 1)繼承Filter類並重寫getDocIdSet()方法
176      * @see 2)根據實際過濾要求返回新的DocIdSet對象
177      * @see ------------------------------------------------------------------------------------------
178      * @see DocIdSet小解
179      * @see 這裏Filter乾的活其實就是建立一個DocIdSet,而DocIdSet其實就是一個數組,能夠理解爲其中只存放0或1的值
180      * @see 每一個搜索出來的Document都有一個文檔編號,因此搜索出來多少個Document,那麼DocIdSet中就會有多少條記錄
181      * @see 而DocIdSet中每一條記錄的索引號與文檔編號是一一對應的
182      * @see 因此當DocIdSet中的記錄爲1時,則對應文檔編號的Document就會被添加到TopDocs中,爲0就會被過濾掉
183      * @see ------------------------------------------------------------------------------------------
184      * @create Aug 6, 2013 7:28:53 PM
185      * @author 玄玉<http://blog.csdn.net/jadyer>
186      */
187     class MyFilter extends Filter {
188         private static final long serialVersionUID = -8955061358165068L;
189 
190         // 假設這是已刪除記錄的fileID值的集合
191         private String[] deleteFileIDs = { "1", "3" };
192 
193         @Override
194         public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
195             // 建立一個DocIdSet的子類OpenBitSet(建立以後默認全部元素都是0),傳的參數就是本次"搜索到的"元素數目
196             OpenBitSet obs = new OpenBitSet(reader.maxDoc());
197             // 先把元素填滿,即所有設置爲1
198             obs.set(0, reader.maxDoc());
199             // 用於保存已刪除元素的文檔編號
200             int[] docs = new int[1];
201             for (String deleteDataID : deleteFileIDs) {
202                 // 獲取已刪除元素對應的TermDocs
203                 TermDocs tds = reader.termDocs(new Term("fileID", deleteDataID));
204                 // 將已刪除元素的文檔編號放到docs中,將其出現的頻率放到freqs中,最後返回查詢出來的元素數目
205                 int count = tds.read(docs, new int[1]);
206                 if (count == 1) {
207                     // 將這個位置docs[0]的元素刪除
208                     obs.clear(docs[0]);
209                 }
210             }
211             return obs;
212         }
213     }
214 
215 }
View Code

  7.高級搜索之自定義QueryParser

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.text.SimpleDateFormat;
  4 import java.util.Date;
  5 import java.util.regex.Pattern;
  6 
  7 import org.apache.lucene.analysis.Analyzer;
  8 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  9 import org.apache.lucene.document.Document;
 10 import org.apache.lucene.document.Field;
 11 import org.apache.lucene.document.NumericField;
 12 import org.apache.lucene.index.IndexReader;
 13 import org.apache.lucene.index.IndexWriter;
 14 import org.apache.lucene.index.IndexWriterConfig;
 15 import org.apache.lucene.queryParser.ParseException;
 16 import org.apache.lucene.queryParser.QueryParser;
 17 import org.apache.lucene.search.IndexSearcher;
 18 import org.apache.lucene.search.NumericRangeQuery;
 19 import org.apache.lucene.search.Query;
 20 import org.apache.lucene.search.ScoreDoc;
 21 import org.apache.lucene.search.TopDocs;
 22 import org.apache.lucene.store.Directory;
 23 import org.apache.lucene.store.FSDirectory;
 24 import org.apache.lucene.util.Version;
 25 
 26 /**
 27  * 【Lucene3.6.2入門系列】第09節_高級搜索之自定義QueryParser
 28  * 
 29  * @create Aug 19, 2013 2:07:32 PM
 30  * @author 玄玉<http://blog.csdn.net/jadyer>
 31  */
 32 public class Lucene_07_AdvancedSearch {
 33     private Directory directory;
 34     private IndexReader reader;
 35 
 36     /**
 37      * 測試一下搜索效果
 38      */
 39     public static void main(String[] args) {
 40         Lucene_07_AdvancedSearch advancedSearch = new Lucene_07_AdvancedSearch();
 41         advancedSearch.searchByCustomQueryParser("name:Jadk~");
 42         advancedSearch.searchByCustomQueryParser("name:Ja??er");
 43         System.out.println("------------------------------------------------------------------------");
 44         advancedSearch.searchByCustomQueryParser("name:Jade");
 45         System.out.println("------------------------------------------------------------------------");
 46         advancedSearch.searchByCustomQueryParser("name:[h TO n]");
 47         System.out.println("------------------------------------------------------------------------");
 48         advancedSearch.searchByCustomQueryParser("size:[20 TO 80]");
 49         System.out.println("------------------------------------------------------------------------");
 50         advancedSearch.searchByCustomQueryParser("date:[20130407 TO 20130701]");
 51     }
 52 
 53     public Lucene_07_AdvancedSearch() {
 54         /** 文件大小 */
 55         int[] sizes = { 90, 10, 20, 10, 60, 50 };
 56         /** 文件名 */
 57         String[] names = { "Michael.java", "Scofield.ini", "Tbag.txt", "Jack", "Jade", "Jadyer" };
 58         /** 文件內容 */
 59         String[] contents = { "my java blog is http://blog.csdn.net/jadyer", "my Java Website is http://www.jadyer.cn", "my name is jadyer", "I am a Java Developer", "I am from Haerbin", "I like java of Lucene" };
 60         /** 文件日期 */
 61         Date[] dates = new Date[sizes.length];
 62         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
 63         IndexWriter writer = null;
 64         Document doc = null;
 65         try {
 66             dates[0] = sdf.parse("20130407 15:25:30");
 67             dates[1] = sdf.parse("20130407 16:30:45");
 68             dates[2] = sdf.parse("20130213 11:15:25");
 69             dates[3] = sdf.parse("20130808 09:30:55");
 70             dates[4] = sdf.parse("20130526 13:54:22");
 71             dates[5] = sdf.parse("20130701 17:35:34");
 72             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 73             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 74             writer.deleteAll();
 75             for (int i = 0; i < sizes.length; i++) {
 76                 doc = new Document();
 77                 doc.add(new NumericField("size", Field.Store.YES, true).setIntValue(sizes[i]));
 78                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
 79                 doc.add(new Field("content", contents[i], Field.Store.NO, Field.Index.ANALYZED));
 80                 doc.add(new NumericField("date", Field.Store.YES, true).setLongValue(dates[i].getTime()));
 81                 writer.addDocument(doc);
 82             }
 83         }
 84         catch (Exception e) {
 85             e.printStackTrace();
 86         }
 87         finally {
 88             if (null != writer) {
 89                 try {
 90                     writer.close();
 91                 }
 92                 catch (IOException ce) {
 93                     ce.printStackTrace();
 94                 }
 95             }
 96         }
 97     }
 98 
 99     /**
100      * 獲取IndexReader實例
101      */
102     private IndexReader getIndexReader() {
103         try {
104             if (reader == null) {
105                 reader = IndexReader.open(directory);
106             }
107             else {
108                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
109                 // 若是當前reader在打開期間index發生改變,則打開並返回一個新的IndexReader,不然返回null
110                 IndexReader ir = IndexReader.openIfChanged(reader);
111                 if (ir != null) {
112                     reader.close(); // 關閉原reader
113                     reader = ir;    // 賦予新reader
114                 }
115             }
116             return reader;
117         }
118         catch (Exception e) {
119             e.printStackTrace();
120         }
121         return null; // 發生異常則返回null
122     }
123 
124     /**
125      * 自定義QueryParser的搜索
126      * 
127      * @param expr
128      *            搜索的表達式
129      */
130     public void searchByCustomQueryParser(String expr) {
131         IndexSearcher searcher = new IndexSearcher(this.getIndexReader());
132         QueryParser parser = new MyQueryParser(Version.LUCENE_36, "content", new StandardAnalyzer(Version.LUCENE_36));
133         try {
134             Query query = parser.parse(expr);
135             TopDocs tds = searcher.search(query, 10);
136             for (ScoreDoc sd : tds.scoreDocs) {
137                 Document doc = searcher.doc(sd.doc);
138                 System.out.print("文檔編號=" + sd.doc + "  文檔權值=" + doc.getBoost() + "  文檔評分=" + sd.score + "    ");
139                 System.out.println("size=" + doc.get("size") + "  date=" + new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date(Long.parseLong(doc.get("date")))) + "  name=" + doc.get("name"));
140             }
141         }
142         catch (ParseException e) {
143             System.err.println(e.getMessage());
144         }
145         catch (Exception e) {
146             e.printStackTrace();
147         }
148         finally {
149             if (null != searcher) {
150                 try {
151                     searcher.close(); // 記得關閉IndexSearcher
152                 }
153                 catch (IOException e) {
154                     e.printStackTrace();
155                 }
156             }
157         }
158     }
159 
160     /**
161      * 自定義QueryParser
162      * 
163      * @see --------------------------------------------------------------------------------------------------
164      * @see 實際使用QueryParser的過程當中,一般會考慮兩個問題
165      * @see 1)限制性能低的QueryParser--對於某些QueryParser在搜索時會使得性能下降,故考慮禁用這些搜索以提高性能
166      * @see 2)擴展基於數字和日期的搜索---有時須要進行一個數字的範圍搜索,故需擴展原有的QueryParser才能實現此搜索
167      * @see --------------------------------------------------------------------------------------------------
168      * @see 限制性能低的QueryParser
169      * @see 繼承QueryParser類並重載相應方法,好比getFuzzyQuery和getWildcardQuery
170      * @see 這樣形成的結果就是,當輸入普通的搜索表達式時,如'I AND Haerbin'能夠正常搜索
171      * @see 但輸入'name:Jadk~'或者'name:Ja??er'時,就會執行到重載方法中,這時就能夠自行處理了,好比本例中禁止該功能
172      * @see --------------------------------------------------------------------------------------------------
173      * @see 擴展基於數字和日期的查詢
174      * @see 思路就是繼承QueryParser類後重載getRangeQuery()方法
175      * @see 再針對數字和日期的'域',作特殊處理(使用NumericRangeQuery.newIntRange()方法來搜索)
176      * @see --------------------------------------------------------------------------------------------------
177      * @create Aug 6, 2013 4:13:42 PM
178      * @author 玄玉<http://blog.csdn.net/jadyer>
179      */
180     public class MyQueryParser extends QueryParser {
181         public MyQueryParser(Version matchVersion, String f, Analyzer a) {
182             super(matchVersion, f, a);
183         }
184 
185         @Override
186         protected Query getWildcardQuery(String field, String termStr) throws ParseException {
187             throw new ParseException("因爲性能緣由,已禁用通配符搜索,請輸入更精確的信息進行搜索 ^_^ ^_^");
188         }
189 
190         @Override
191         protected Query getFuzzyQuery(String field, String termStr, float minSimilarity) throws ParseException {
192             throw new ParseException("因爲性能緣由,已禁用模糊搜索,請輸入更精確的信息進行搜索 ^_^ ^_^");
193         }
194 
195         @Override
196         protected Query getRangeQuery(String field, String part1, String part2, boolean inclusive) throws ParseException {
197             if (field.equals("size")) {
198                 // 默認的QueryParser.parse(String query)表達式中並不支持'size:[20 TO 80]'數字的域值
199                 // 這樣一來,針對數字的域值進行特殊處理,那麼QueryParser表達式就支持數字了
200                 return NumericRangeQuery.newIntRange(field, Integer.parseInt(part1), Integer.parseInt(part2), inclusive, inclusive);
201             }
202             else if (field.equals("date")) {
203                 String regex = "\\d{8}";
204                 String dateType = "yyyyMMdd";
205                 if (Pattern.matches(regex, part1) && Pattern.matches(regex, part2)) {
206                     SimpleDateFormat sdf = new SimpleDateFormat(dateType);
207                     try {
208                         long min = sdf.parse(part1).getTime();
209                         long max = sdf.parse(part2).getTime();
210                         // 使之支持日期的檢索,應用時直接QueryParser.parse("date:[20130407 TO 20130701]")
211                         return NumericRangeQuery.newLongRange(field, min, max, inclusive, inclusive);
212                     }
213                     catch (java.text.ParseException e) {
214                         e.printStackTrace();
215                     }
216                 }
217                 else {
218                     throw new ParseException("Unknown date format, please use '" + dateType + "'");
219                 }
220             }
221             // 如沒找到匹配的Field域,那麼返回默認的TermRangeQuery
222             return super.getRangeQuery(field, part1, part2, inclusive);
223         }
224     }
225 }
View Code

  8.高亮

  1 import java.io.File;
  2 import java.io.IOException;
  3 
  4 import org.apache.lucene.analysis.Analyzer;
  5 import org.apache.lucene.document.Document;
  6 import org.apache.lucene.document.Field;
  7 import org.apache.lucene.index.IndexReader;
  8 import org.apache.lucene.index.IndexWriter;
  9 import org.apache.lucene.index.IndexWriterConfig;
 10 import org.apache.lucene.queryParser.MultiFieldQueryParser;
 11 import org.apache.lucene.queryParser.QueryParser;
 12 import org.apache.lucene.search.IndexSearcher;
 13 import org.apache.lucene.search.Query;
 14 import org.apache.lucene.search.ScoreDoc;
 15 import org.apache.lucene.search.TopDocs;
 16 import org.apache.lucene.search.highlight.Formatter;
 17 import org.apache.lucene.search.highlight.Fragmenter;
 18 import org.apache.lucene.search.highlight.Highlighter;
 19 import org.apache.lucene.search.highlight.QueryScorer;
 20 import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
 21 import org.apache.lucene.search.highlight.SimpleSpanFragmenter;
 22 import org.apache.lucene.store.Directory;
 23 import org.apache.lucene.store.FSDirectory;
 24 import org.apache.lucene.util.Version;
 25 import org.apache.tika.Tika;
 26 
 27 import com.chenlb.mmseg4j.analysis.MMSegAnalyzer;
 28 
 29 /**
 30  * 【Lucene3.6.2入門系列】第11節_高亮
 31  * 
 32  * @see 高亮功能屬於Lucene的擴展功能(或者叫作貢獻功能)
 33  * @see 其所需jar位於Lucene-3.6.2.zip中的/contrib/highlighter/文件夾中
 34  * @see 本例中須要如下4個jar
 35  * @see lucene-core-3.6.2.jar
 36  * @see lucene-highlighter-3.6.2.jar
 37  * @see mmseg4j-all-1.8.5-with-dic.jar
 38  * @see tika-app-1.4.jar
 39  * @create Aug 7, 2013 11:37:10 AM
 40  * @author 玄玉<http://blog.csdn.net/jadyer>
 41  */
 42 public class Lucene_08_HelloHighLighter {
 43     private Directory directory;
 44     private IndexReader reader;
 45 
 46     public Lucene_08_HelloHighLighter() {
 47         Document doc = null;
 48         IndexWriter writer = null;
 49         try {
 50             directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 51             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new MMSegAnalyzer()));
 52             writer.deleteAll();
 53             for (File myFile : new File("E:/lucene_test/01_index/").listFiles()) {
 54                 doc = new Document();
 55                 doc.add(new Field("filecontent", new Tika().parse(myFile))); // Field.Store.NO,Field.Index.ANALYZED
 56                 doc.add(new Field("filepath", myFile.getAbsolutePath(), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
 57                 writer.addDocument(doc);
 58             }
 59         }
 60         catch (Exception e) {
 61             e.printStackTrace();
 62         }
 63         finally {
 64             if (null != writer) {
 65                 try {
 66                     writer.close();
 67                 }
 68                 catch (IOException ce) {
 69                     ce.printStackTrace();
 70                 }
 71             }
 72         }
 73     }
 74 
 75     /**
 76      * 獲取IndexSearcher實例
 77      */
 78     private IndexSearcher getIndexSearcher() {
 79         try {
 80             if (reader == null) {
 81                 reader = IndexReader.open(directory);
 82             }
 83             else {
 84                 // if the index was changed since the provided reader was opened, open and return a new reader; else,return null
 85                 // 若是當前reader在打開期間index發生改變,則打開並返回一個新的IndexReader,不然返回null
 86                 IndexReader ir = IndexReader.openIfChanged(reader);
 87                 if (ir != null) {
 88                     reader.close(); // 關閉原reader
 89                     reader = ir;    // 賦予新reader
 90                 }
 91             }
 92             return new IndexSearcher(reader);
 93         }
 94         catch (Exception e) {
 95             e.printStackTrace();
 96         }
 97         return null; // 發生異常則返回null
 98     }
 99 
100     /**
101      * 高亮搜索
102      * 
103      * @see 高亮搜索時,不建議把高亮信息存到索引裏,而是搜索到內容以後再進行高亮處理
104      * @see 這裏用的是MMSeg4j中文分詞器,有關其介紹詳見http://blog.csdn.net/jadyer/article/details/10049525
105      * @param expr
106      *            搜索表達式
107      */
108     public void searchByHignLighter(String expr) {
109         Analyzer analyzer = new MMSegAnalyzer();
110         IndexSearcher searcher = this.getIndexSearcher();
111         // 搜索多個Field
112         QueryParser parser = new MultiFieldQueryParser(Version.LUCENE_36, new String[] { "filepath", "filecontent" }, analyzer);
113         try {
114             Query query = parser.parse(expr);
115             TopDocs tds = searcher.search(query, 50);
116             for (ScoreDoc sd : tds.scoreDocs) {
117                 Document doc = searcher.doc(sd.doc);
118                 // 獲取文檔內容
119                 String filecontent = new Tika().parseToString(new File(doc.get("filepath")));
120                 System.out.println("搜索到的內容爲[" + filecontent + "]");
121                 // 開始高亮處理
122                 QueryScorer queryScorer = new QueryScorer(query);
123                 Fragmenter fragmenter = new SimpleSpanFragmenter(queryScorer, filecontent.length());
124                 Formatter formatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
125                 Highlighter hl = new Highlighter(formatter, queryScorer);
126                 hl.setTextFragmenter(fragmenter);
127                 System.out.println("高亮後的內容爲[" + hl.getBestFragment(analyzer, "filecontent", filecontent) + "]");
128             }
129         }
130         catch (Exception e) {
131             e.printStackTrace();
132         }
133         finally {
134             if (null != searcher) {
135                 try {
136                     searcher.close(); // 記得關閉IndexSearcher
137                 }
138                 catch (IOException e) {
139                     e.printStackTrace();
140                 }
141             }
142         }
143     }
144 
145     /**
146      * 高亮的使用方式
147      * 
148      * @see 這裏用的是MMSeg4j中文分詞器,有關其介紹詳見http://blog.csdn.net/jadyer/article/details/10049525
149      */
150     private static void testHighLighter() {
151         String fieldName = "myinfo"; // 這個能夠隨便寫,就是起個標識的做用
152         String text = "我來自中國黑龍江省哈爾濱市巴彥縣興隆鎮長春鄉民權村4隊";
153         QueryParser parser = new QueryParser(Version.LUCENE_36, fieldName, new MMSegAnalyzer());
154         try {
155             // MMSeg4j的new MMSegAnalyzer()默認只會對'中國'和'興隆'進行分詞,因此這裏就只高亮它們倆了
156             Query query = parser.parse("中國 興隆");
157             // 針對查詢出來的文本,查詢其評分,以便於可以根據評分決定顯示狀況
158             QueryScorer queryScorer = new QueryScorer(query);
159             // 對字符串或文本進行分段,SimpleSpanFragmenter構造方法的第二個參數能夠指定高亮的文本長度,默認爲100
160             Fragmenter fragmenter = new SimpleSpanFragmenter(queryScorer);
161             // 高亮時的高亮格式,默認爲<B></B>,這裏指定爲紅色字體
162             Formatter formatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
163             // Highlighter專門用來作高亮顯示
164             // 該構造方法還有一個參數爲Encoder,它有兩個實現類DefaultEncoder和SimpleHTMLEncoder
165             // SimpleHTMLEncoder能夠忽略掉HTML標籤,而DefaultEncoder則不會忽略HTML標籤
166             Highlighter hl = new Highlighter(formatter, queryScorer);
167             hl.setTextFragmenter(fragmenter);
168             System.out.println(hl.getBestFragment(new MMSegAnalyzer(), fieldName, text));
169         }
170         catch (Exception e) {
171             e.printStackTrace();
172         }
173     }
174 
175     /**
176      * 小測試一下
177      */
178     public static void main(String[] args) {
179         // 測試高亮的基本使用效果
180         Lucene_08_HelloHighLighter.testHighLighter();
181         // 測試高亮搜索的效果(測試前記得在myExample/myFile/文件夾中準備一個或多個內容包含"依賴"的doc或pdf的等文件)
182         // new Lucene_08_HelloHighLighter().searchByHignLighter("依賴");
183     }
184 }
View Code

  9.近實時搜索

  1 import java.io.File;
  2 import java.io.IOException;
  3 
  4 import org.apache.lucene.analysis.standard.StandardAnalyzer;
  5 import org.apache.lucene.document.Document;
  6 import org.apache.lucene.document.Field;
  7 import org.apache.lucene.index.IndexReader;
  8 import org.apache.lucene.index.IndexWriter;
  9 import org.apache.lucene.index.IndexWriterConfig;
 10 import org.apache.lucene.index.Term;
 11 import org.apache.lucene.search.IndexSearcher;
 12 import org.apache.lucene.search.NRTManager;
 13 import org.apache.lucene.search.NRTManager.TrackingIndexWriter;
 14 import org.apache.lucene.search.NRTManagerReopenThread;
 15 import org.apache.lucene.search.Query;
 16 import org.apache.lucene.search.ScoreDoc;
 17 import org.apache.lucene.search.TermQuery;
 18 import org.apache.lucene.search.TopDocs;
 19 import org.apache.lucene.store.Directory;
 20 import org.apache.lucene.store.FSDirectory;
 21 import org.apache.lucene.util.Version;
 22 
 23 /**
 24  * 【Lucene3.6.2入門系列】第12節_近實時搜索
 25  * 
 26  * @see 實時搜索(near-real-time)---->只要數據發生變化,則立刻更新索引(IndexWriter.commit())
 27  * @see 近實時搜索------------------>數據發生變化時,先將索引保存到內存中,而後在一個統一的時間再對內存中的全部索引執行commit提交動做
 28  * @see 爲了實現近實時搜索,Lucene3.0提供的方式叫作reopen,後來的版本中提供了兩個線程安全的類NRTManager和SearcherManager
 29  * @see 不過這倆線程安全的類在Lucene3.5和3.6版本中的用法有點不太同樣,這點要注意
 30  * @create Aug 7, 2013 4:19:58 PM
 31  * @author 玄玉<http://blog.csdn.net/jadyer>
 32  */
 33 public class Lucene_09_HelloNRTSearch {
 34     private IndexWriter writer;
 35     private NRTManager nrtManager;
 36     private TrackingIndexWriter trackWriter;
 37 
 38     /**
 39      * 測試時,要在E:/lucene_test/01_file/文件夾中準備幾個包含內容的文件(好比txt格式的)
 40      * 而後先執行createIndex()方法,再執行searchFile()方法,最後觀看控制檯輸出便可
 41      */
 42     public static void main(String[] args) {
 43         Lucene_09_HelloNRTSearch instance = new Lucene_09_HelloNRTSearch();
 44         instance.createIndex();
 45         instance.testSearchFile();
 46         instance.getDocsCount();
 47     }
 48 
 49     public void testSearchFile() {
 50         Lucene_09_HelloNRTSearch hello = new Lucene_09_HelloNRTSearch();
 51         for (int i = 0; i < 5; i++) {
 52             hello.searchFile();
 53             System.out.println("-----------------------------------------------------------");
 54             hello.deleteIndex();
 55             if (i == 2) {
 56                 hello.updateIndex();
 57             }
 58             try {
 59                 System.out.println(".........開始休眠2s(模擬近實時搜索情景)");
 60                 Thread.sleep(2000);
 61                 System.out.println(".........休眠結束");
 62             }
 63             catch (InterruptedException e) {
 64                 e.printStackTrace();
 65             }
 66         }
 67         // 不能單獨去new HelloNRTSearch,要保證它們是同一個對象,不然所作的delete和update不會被commit
 68         hello.commitIndex();
 69     }
 70 
 71     public Lucene_09_HelloNRTSearch() {
 72         try {
 73             Directory directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
 74             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
 75             trackWriter = new NRTManager.TrackingIndexWriter(writer);
 76             // /*
 77             // * Lucene3.5中的NRTManager是經過下面的方式建立的
 78             // * 而且Lucene3.5中能夠直接使用NRTManager.getSearcherManager(true)獲取到org.apache.lucene.search.SearcherManager
 79             // */
 80             // nrtManager = new NRTManager(writer,new org.apache.lucene.search.SearcherWarmer() {
 81             // @Override
 82             // public void warm(IndexSearcher s) throws IOException {
 83             // System.out.println("IndexSearcher.reopen時會自動調用此方法");
 84             // }
 85             // });
 86             nrtManager = new NRTManager(trackWriter, null);
 87             // 啓動一個Lucene提供的後臺線程來自動定時的執行NRTManager.maybeRefresh()方法
 88             // 這裏的後倆參數,是根據這篇分析的文章寫的http://blog.mikemccandless.com/2011/11/near-real-time-readers-with-lucenes.html
 89             NRTManagerReopenThread reopenThread = new NRTManagerReopenThread(nrtManager, 5.0, 0.025);
 90             reopenThread.setName("NRT Reopen Thread");
 91             reopenThread.setDaemon(true);
 92             reopenThread.start();
 93         }
 94         catch (Exception e) {
 95             e.printStackTrace();
 96         }
 97     }
 98 
 99     /**
100      * 建立索引
101      */
102     public void createIndex() {
103         String[] ids = { "1", "2", "3", "4", "5", "6" };
104         String[] names = { "Michael", "Scofield", "Tbag", "Jack", "Jade", "Jadyer" };
105         String[] contents = { "my blog", "my website", "my name", "my job is JavaDeveloper", "I am from Haerbin", "I like Lucene" };
106         IndexWriter writer = null;
107         Document doc = null;
108         try {
109             Directory directory = FSDirectory.open(new File("E:/lucene_test/01_index/"));
110             writer = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
111             writer.deleteAll();
112             for (int i = 0; i < names.length; i++) {
113                 doc = new Document();
114                 doc.add(new Field("id", ids[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
115                 doc.add(new Field("name", names[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
116                 doc.add(new Field("content", contents[i], Field.Store.YES, Field.Index.ANALYZED));
117                 writer.addDocument(doc);
118             }
119         }
120         catch (Exception e) {
121             e.printStackTrace();
122         }
123         finally {
124             if (null != writer) {
125                 try {
126                     writer.close();
127                 }
128                 catch (IOException ce) {
129                     ce.printStackTrace();
130                 }
131             }
132         }
133     }
134 
135     /**
136      * 經過IndexReader獲取文檔數量
137      */
138     public void getDocsCount() {
139         IndexReader reader = null;
140         try {
141             reader = IndexReader.open(FSDirectory.open(new File("E:/lucene_test/01_index/")));
142             System.out.println("maxDocs:" + reader.maxDoc());
143             System.out.println("numDocs:" + reader.numDocs());
144             System.out.println("deletedDocs:" + reader.numDeletedDocs());
145         }
146         catch (Exception e) {
147             e.printStackTrace();
148         }
149         finally {
150             if (reader != null) {
151                 try {
152                     reader.close();
153                 }
154                 catch (IOException e) {
155                     e.printStackTrace();
156                 }
157             }
158         }
159     }
160 
161     /**
162      * 搜索文件
163      */
164     public void searchFile() {
165         // Lucene3.5裏面能夠直接使用NRTManager.getSearcherManager(true).acquire()
166         IndexSearcher searcher = nrtManager.acquire();
167         Query query = new TermQuery(new Term("content", "my"));
168         try {
169             TopDocs tds = searcher.search(query, 10);
170             for (ScoreDoc sd : tds.scoreDocs) {
171                 Document doc = searcher.doc(sd.doc);
172                 System.out.print("文檔編號=" + sd.doc + "  文檔權值=" + doc.getBoost() + "  文檔評分=" + sd.score + "    ");
173                 System.out.println("id=" + doc.get("id") + "  name=" + doc.get("name") + "  content=" + doc.get("content"));
174             }
175         }
176         catch (Exception e) {
177             e.printStackTrace();
178         }
179         finally {
180             try {
181                 // 這裏就不要IndexSearcher.close()啦,而是交由NRTManager來釋放
182                 nrtManager.release(searcher);
183                 // Lucene-3.6.2文檔中ReferenceManager.acquire()方法描述裏建議再手工設置searcher爲null,以防止在其它地方被意外的使用
184                 searcher = null;
185             }
186             catch (IOException e) {
187                 e.printStackTrace();
188             }
189         }
190     }
191 
192     /**
193      * 更新索引
194      */
195     public void updateIndex() {
196         Document doc = new Document();
197         doc.add(new Field("id", "11", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
198         doc.add(new Field("name", "xuanyu", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
199         doc.add(new Field("content", "my name is xuanyu", Field.Store.YES, Field.Index.ANALYZED));
200         try {
201             // Lucene3.5中能夠直接使用org.apache.lucene.search.NRTManager.updateDocument(new Term("id", "1"), doc)
202             trackWriter.updateDocument(new Term("id", "1"), doc);
203         }
204         catch (IOException e) {
205             e.printStackTrace();
206         }
207     }
208 
209     /**
210      * 刪除索引
211      */
212     public void deleteIndex() {
213         try {
214             // Lucene3.5中能夠直接使用org.apache.lucene.search.NRTManager.deleteDocuments(new Term("id", "2"))
215             trackWriter.deleteDocuments(new Term("id", "2"));
216         }
217         catch (IOException e) {
218             e.printStackTrace();
219         }
220     }
221 
222     /**
223      * 提交索引內容的變動狀況
224      */
225     public void commitIndex() {
226         try {
227             writer.commit();
228         }
229         catch (IOException e) {
230             e.printStackTrace();
231         }
232     }
233 }
View Code

參考文章

  http://www.chedong.com/tech/lucene.html

  http://blog.csdn.net/column/details/jadyerlucene.html

相關文章
相關標籤/搜索