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...) |
索引數據源: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關係等 |
|
併發訪問 | 可以支持多用戶的使用 |
對於中文來講,全文索引首先還要解決一個語言分析的問題,對於英文來講,語句中單詞之間是自然經過空格分開的,但亞洲語言的中日韓文語句中的字是一個字挨一個,因此,首先要把語句中按「詞」進行索引的話,這個詞如何切分出來就是一個很大的問題。
首先,確定不能用單個字符做(si-gram)爲索引單元,不然查「上海」時,不能讓含有「海上」也匹配。
但一句話:「北京天安門」,計算機如何按照中文的語言習慣進行切分呢?
「北京 天安門」 仍是「北 京 天安門」?讓計算機可以按照語言習慣進行切分,每每須要機器有一個比較豐富的詞庫纔可以比較準確的識別出語句中的單詞。
另一個解決的辦法是採用自動切分算法:將單詞按照2元語法(bigram)方式切分出來,好比:"北京天安門" ==> "北京 京天 天安 安門"。
這樣,在查詢的時候,不管是查詢"北京" 仍是查詢"天安門",將查詢詞組按一樣的規則進行切分:"北京","天安安門",多個關鍵詞之間按與"and"的關係組合,一樣可以正確地映射到相應的索引中。
這種方式對於其餘亞洲語言:韓文,日文都是通用的。
基於自動切分的最大優勢是沒有詞表維護成本,實現簡單,缺點是索引效率低,但對於中小型應用來講,基於2元語法的切分仍是夠用的。
基於2元切分後的索引通常大小和源文件差很少,而對於英文,索引文件通常只有原文件的30%-40%不一樣,
自動切分 | 詞表切分 | |
實現 | 實現很是簡單 | 實現複雜 |
查詢 | 增長了查詢分析的複雜程度, | 適於實現比較複雜的查詢語法規則 |
存儲效率 | 索引冗餘大,索引幾乎和原文同樣大 | 索引效率高,爲原文大小的30%左右 |
維護成本 | 無詞表維護成本 | 詞表維護成本很是高:中日韓等語言須要分別維護。 還須要包括詞頻統計等內容 |
適用領域 | 嵌入式系統:運行環境資源有限 分佈式系統:無詞表同步問題 多語言環境:無詞表維護成本 |
對查詢和存儲效率要求高的專業搜索引擎 |
目前比較大的搜索引擎的語言分析算法通常是基於以上2個機制的結合。
對於外部應用來講索引模塊(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 | 只全文索引,不存儲 |
索引過程當中能夠看到:
檢索過程和結果顯示:
目前LUCENE支持的語法:
Query ::= ( Clause )*
Clause ::= ["+", "-"] [<TERM> ":"] ( <TERM> | "(" Query ")")
中間的邏輯包括:and or + - &&||等符號,並且還有"短語查詢"和針對西文的前綴/模糊查詢等,對於通常應用來講,這些功能有一些華而不實,其實可以實現目前相似於Google的查詢語句分析功能其實對於大多數用戶來講已經夠了。
因此,Lucene早期版本的QueryParser還是比較好的選擇。
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的另一個特色是在收集結果的過程當中將匹配度低的結果自動過濾掉了。這也是和數據庫應用須要將搜索的結果所有返回不一樣之處。
Luene的確是一個面向對象設計的典範
這些優勢都是很是值得在之後的開發中學習借鑑的。做爲一個通用工具包,Lunece的確給予了須要將全文檢索功能嵌入到應用中的開發者不少的便利。
此外,經過對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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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 }
http://www.chedong.com/tech/lucene.html
http://blog.csdn.net/column/details/jadyerlucene.html