Lucene 全文檢索
Lucene 全文檢索
Field域
- Field是文件中的域,包括Field名和Field值兩部分,一個文件可以包括多個Field,Document只是Field的一個承載體,Field值即為要索引的內容,也是要搜尋的內容。
是否分詞
id
是否索引
-
索引的目的就是為了將來作為查詢條件來搜尋,比如商品的名稱,商品的介紹,文章的內容,這些內容需要輸入關鍵詞搜尋的,我們必須進行索引,如果不索引將會不能愛按照這些內容搜尋。
-
不索引: 商品的
id
,圖片的路徑等這個是不需要作為查詢條件的,因此不需要索引
是否儲存
- 將Field值儲存在文件中,儲存在文件中的Field才可以從
Document
中獲取。 - 比如:商品名稱、訂單號,凡是將來要從
Document
中獲取的Field
都要儲存。 - 否:不儲存Field值,不儲存的Field無法通過
Document
獲取 - 比如:商品簡介,內容較大不用儲存。如果要向用戶展示商品簡介可以從系統的關係資料庫中獲取商品簡介。
- 如果需要商品描述,則根據搜尋出的商品ID去資料庫中查詢,然後顯示出商品描述資訊即可。
Field的常用型別
-
型別
Field改進
- 圖書id
- 是否分詞:不用分詞,因為不會根據商品id來搜尋商品
- 是否索引:不索引,因為不需要根據圖書ID進行搜尋
- 是否儲存:要儲存,因為查詢結果頁面需要使用id這個值。
- 圖書名稱:
- 是否分詞:要分詞,因為要將圖書的名稱內容分詞索引,根據關鍵搜尋圖書名稱抽取的詞。
- 是否索引:要索引。
- 是否儲存:要儲存。
- 圖書價格
- 是否分詞:要分詞,lucene對數字型的值只要有搜尋需求的都要分詞和索 引,因為lucene對數字型的內容要特殊分詞處理,本例子可能要根據價格範 圍搜尋,需要分詞和索引。
- 是否索引:要索引 是否儲存:要儲存
- 圖書圖片地址:
- 是否分詞:不分詞
- 是否索引:不索引
- 是否儲存:要儲存,因為只有根據圖片地址才能找到對應的圖片
- 圖書描述:
- 是否分詞:要分詞
- 是否索引:要索引
- 是否儲存:因為圖書描述內容量大,不在查詢結果頁面直接顯示,不儲存。 不儲存是來不在lucene的索引檔案中記錄,節省lucene的索引檔案空間, 如果要在詳情頁面顯示描述,思路: 從lucene中取出圖書的id,根據圖書的id查詢關係資料庫中book表 得到描述資訊。
新增依賴
- 這裡使用的
IKAnalyzer
這個中文分詞器
<!-- 新增lucene支援 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-core</artifactId> <version>4.10.3</version> </dependency> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>4.10.3</version> </dependency> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>4.10.3</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-highlighter</artifactId> <version>4.10.3</version> </dependency> <!-- IK分詞器 --> <dependency> <groupId>com.janeluo</groupId> <artifactId>ikanalyzer</artifactId> <version>2012_u6</version> </dependency>
新增IK中文分詞器的擴充套件
- 只需要將 ofollow,noindex">這些檔案下載 下載出來,然後新增到
src/main/resource
路徑下即可
Lucene的工具類
- 其中自己封裝了一些方法
/** * Lucene的工具類 * @author chenjiabing */ public class LuceneUtils { /** * 獲取IndexWriter用於建立索引庫 * @return IndexWriter物件 * @throws Exception */ public static IndexWriter getIndexWriter() throws Exception{ //建立索引庫存放的位置 Directory directory=FSDirectory.open(new File("/home/chenjiabing/Documents/Lucene")); //使用IK中文分詞器 IKAnalyzer ikAnalyzer=new IKAnalyzer(true); //建立IndexWriteConfig物件,其中傳入的是分析器物件 IndexWriterConfig indexWriterConfig=new IndexWriterConfig(Version.LATEST, ikAnalyzer); //建立索引,其中的變數是索引庫的位置,索引配置物件 IndexWriter indexWriter=new IndexWriter(directory, indexWriterConfig); return indexWriter; } /** * 獲取IndexSearcher,用於查詢 * @return IndexSearcher物件 * @throws Exception */ public static IndexSearcher getIndexSearcher()throws Exception{ //建立Directory物件,指定索引庫的位置 Directory directory=FSDirectory.open(new File("/home/chenjiabing/Documents/Lucene")); //建立IndexReader物件 IndexReader indexReader=DirectoryReader.open(directory); //建立IndexSearcher物件 IndexSearcher indexSearcher=new IndexSearcher(indexReader); return indexSearcher; } /** * 根據查詢語句,列印結果 * @param indexSearcherIndexSearch物件 * @param query查詢物件 * @param n顯示結果數量 * @throws IOException */ public static void doSearch(IndexSearcher indexSearcher,Query query,Integer n) throws IOException{ //執行查詢 TopDocs topDocs=indexSearcher.search(query, n); //返回查詢結果 ScoreDoc[] scoreDocs=topDocs.scoreDocs; //遍歷查詢結果 for (ScoreDoc scoreDoc : scoreDocs) { int doc=scoreDoc.doc;//返回文件的編號 //根據編號查詢文件 Document document=indexSearcher.doc(doc); //輸出文件中定義的域 System.out.println(document.get("fileName")); /*System.out.println(document.get("fileSize")); System.out.println(document.get("fileContent")); System.out.println(document.get("filePath"));*/ } } }
建立索引庫
// 建立索引 @Test public void testIndex() throws Exception { IndexWriter indexWriter=LuceneUtils.getIndexWriter(); //建立File物件,這裡是需要索引的檔案 File file=new File("/home/chenjiabing/Documents/Blog"); //獲取資料夾下的所有檔案 File[] files=file.listFiles(); //遍歷所有的檔案,取出相關 的內容 for (File f : files) { Document document=new Document();//建立文件物件 //獲取檔名 String fileName=f.getName(); //建立域檔名分詞,索引 儲存 Field fieldName=newTextField("fileName", fileName, Store.YES); //獲取檔案大小 Long fileSize=FileUtils.sizeOf(f); //建立檔案大小的域,分詞,索引,儲存 Field fieldSize=new LongField("fileSize", fileSize, Store.YES); //獲取檔案路徑 String filePath=f.getPath(); //建立檔案路徑的域 不分詞,不索引 但是必須儲存,用來找到指定的檔案 Field fieldPath=new StoredField("filePath", filePath); //獲取檔案內容 String fileContent=FileUtils.readFileToString(f); //建立檔案內容的域,分詞,索引,儲存 Field fieldContent=new TextField("fileContent", fileContent,Store.YES); //將分出的這些域新增到文件中 document.add(fieldContent); document.add(fieldName); document.add(fieldPath); document.add(fieldSize); //將文件寫入索引庫 indexWriter.addDocument(document); } //關閉IndexWriter物件 indexWriter.close(); }
Query 搜尋
TermQuery
- 這個是精確搜尋
-
TermQuery
項查詢,TermQuery不使用分析器
,搜尋關鍵詞作為整體來匹配Field域中的詞進行查詢,比如訂單號、分類ID號等。- 這個查詢的方式不會通過分詞器進行分詞查詢,而是整個內容匹配。比如其中的查詢條件為檔案的名稱:
springmvc攔截器
,那麼只有當名稱為...springmvc攔截器.....
這樣整個詞語連線在一起的時候才會查詢到
- 這個查詢的方式不會通過分詞器進行分詞查詢,而是整個內容匹配。比如其中的查詢條件為檔案的名稱:
- ``TermQuery(new Term("域名","搜尋的詞語"))
: 這裡的域名是建立Field的時候指定的:
new Field("域名",值)`
@Test public void testTermQuerySearch() throws Exception{ //獲取IndexSearcher IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher(); //建立一個TermQuery物件,指定查詢的域和需要查詢的詞 TermQuery query=new TermQuery(new Term("fileName","springmvc攔截器")); System.out.println(query); //列印查詢結果 LuceneUtils.doSearch(indexSearcher, query, 10); //關閉IndexReader indexSearcher.getIndexReader().close(); }
NumericRangeQuery
- 指定數字範圍查詢.
- 適合查詢價格等數字型別的
- 其中有許多建立查詢範圍的靜態方法,適合多種資料型別的查詢
@Test public void testNumericRangeQuery() throws Exception{ IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher(); /** * 這裡是查詢檔案大小的域:fileSize * 第一個引數: 域名 * 第二個引數:最小值 * 第三個引數: 最大值 * 第四個引數: 是否包含最小值 * 第五個引數: 是否包含最大值 */ Query query=NumericRangeQuery.newLongRange("fileSize", 1000L, 2000L, true, true); //輸出查詢條件:fileSize:[1000 TO 2000] System.out.println(query); LuceneUtils.doSearch(indexSearcher, query, 10); //關閉IndexReader indexSearcher.getIndexReader().close(); }
BooleanQuery
-
BooleanQuery,布林查詢,實現組合條件查詢。
-
Occur.MUST
: 當前的查詢條件必須滿足 -
Occur.SHOULD
: 當前的查詢條件可滿足可不滿足,相當於or
-
MUST_NOT
:查詢條件不能滿足,相當於NOT
非+
@Test public void testBooleanQuery() throws Exception{ IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher(); //第一個查詢條件 根據fileName域查詢 Query query1=new TermQuery(new Term("fileName", "springmvc")); //第二個查詢條件,根據fileSize查詢 Query query2=NumericRangeQuery.newLongRange("fileSize", 1000L, 2000L, true, true); //建立BooleanQuery BooleanQuery query=new BooleanQuery(); //新增查詢條件,這個條件是必須滿足的 :Occur.MUST query.add(query1,Occur.MUST); //新增第二個查詢條件,這個條件可滿足可不滿足,相當於or query.add(query2,Occur.SHOULD); System.out.println(query); //執行查詢 LuceneUtils.doSearch(indexSearcher, query, 10); indexSearcher.getIndexReader().close(); }
MatchAllDocsQuery
- 查詢所有
- 返回的是索引庫中所有的檔案資訊
//查詢所有 @Test public void testMatchAllDoc() throws Exception{ IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher(); Query query=new MatchAllDocsQuery(); System.out.println(query); LuceneUtils.doSearch(indexSearcher, query, 10); indexSearcher.getIndexReader().close(); }
QueryParser [常用]
- 通過
QueryParser
也可以建立Query,QueryParser提供一個Parse
方法,此方法可以直接根據查詢語法來查詢
@Test public void testQueryParser() throws Exception{ IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher(); /** * 第一個引數:指定了預設的查詢的域名 * 第二個引數: 指定了分詞器 */ QueryParser parser=new QueryParser("fileName",new IKAnalyzer()); /** * 其中的字串的形式為: *:* * 查詢所有: *:* * 根據預設域名查詢直接寫一個查詢內容即可: "springmvc" * 根據指定域名查詢: "fileContent:springmvc" */ Query query=parser.parse("fileContent:攔截器"); System.out.println(query); LuceneUtils.doSearch(indexSearcher, query, 50); indexSearcher.getIndexReader().close(); }
MultiFieldQueryParser
- 通過MultiFieldQueryParser對多個域查詢。
@Test public void testMulitFiledQueryParser() throws Exception{ IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher(); //指定預設查詢的域名 String[] fields={"fileName","fileContent"}; /** * 建立物件 * 第一個引數: 指定預設域名的陣列 * 第二引數: 指定分詞器,這裡使用中文分詞器 */ MultiFieldQueryParser parser=new MultiFieldQueryParser(fields, new IKAnalyzer()); //這裡沒有指定域名,因此使用上面指定的兩個預設的域名進行查詢,這兩個預設域名之間是or關係,只要滿足就查詢返回 //Query query=parser.parse("springmvc"); //指定filePath域名中搜索 Query query=parser.parse("filePath:/home/chenjiabing"); System.out.println(query); LuceneUtils.doSearch(indexSearcher, query, 50); indexSearcher.getIndexReader().close(); }
索引維護
- 每個document代表一個索引,維護索引其實就是對
document
的增刪改查
新增索引
indexWriter.addDocument(doc)
刪除索引
指定條件刪除
Term ID writer.deleteDocuments(new Term("域名","值"));
@Test public void deleteIndex() throws Exception { // 建立分詞器,標準分詞器 Analyzer analyzer = new IKAnalyzer(); // 建立IndexWriter IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer); Directory directory = FSDirectory .open(new File("E:\\11-index\\hcx\\")); // 建立IndexWriter IndexWriter writer = new IndexWriter(directory, cfg); // Terms writer.deleteDocuments(new Term("id", "1")); writer.close(); }
刪除全部
- 將索引目錄的索引資訊全部刪除,直接徹底刪除,無法恢復。慎用!
// 刪除索引 @Test public void deleteIndex() throws Exception { // 1、指定索引庫目錄 Directory directory = FSDirectory.open(new File("E:\\11-index\\0720")); // 2、建立IndexWriterConfig IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST, new StandardAnalyzer()); // 3、 建立IndexWriter IndexWriter writer = new IndexWriter(directory, cfg); // 4、通過IndexWriter來刪除索引 // a)、刪除全部索引 writer.deleteAll(); // 5、關閉IndexWriter writer.close(); }
修改索引
- 更新索引是先刪除再新增,建議對更新需求採用此方法並且要保證對已存在的索引執行更新,可以先查詢出來,確定更新記錄存在執行更新操作。
@Test public void updateIndex() throws Exception { // 建立分詞器,標準分詞器 Analyzer analyzer = new StandardAnalyzer(); // 建立IndexWriter IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer); Directory directory = FSDirectory .open(new File("E:\\11-index\\hcx\\")); // 建立IndexWriter IndexWriter writer = new IndexWriter(directory, cfg); // 第一個引數:指定查詢條件 // 第二個引數:修改之後的物件 // 修改時如果根據查詢條件,可以查詢出結果,則將以前的刪掉,然後覆蓋新的Document物件,如果沒有查詢出結果,則新增一個Document // 修改流程即:先查詢,再刪除,在新增 Document doc = new Document(); doc.add(new TextField("name", "lisi", Store.YES)); writer.updateDocument(new Term("name", "zhangsan"), doc); writer.close(); }