**全文檢索工具_Lucence*
阿新 • • 發佈:2018-12-21
課程計劃:
1. 什麼是全文檢索,如何實現全文檢索
2. Lucene實現全文檢索的流程
* 建立索引
* 查詢索引
3. 配置開發環境
4. 入門程式
5. 分析器的分析過程
1. 測試分析器的分析效果
2. 第三方中文分析器
6. 索引庫維護
1. 新增文件
2. 刪除文件
3. 修改文件
7. 索引庫查詢
1. 使用Query子類查詢
2. 使用QueryParser查詢
全文檢索概述
1. 資料分類: 1. 結構化資料:指具有固定格式或有限長度的資料,如資料庫,元資料等。 [資料型別固定] 2. 非結構化資料:指不定長或無固定格式的資料。如郵件,word文件等磁碟上的檔案 [資料型別不固定] 2. 結構化資料搜尋: 1. 常見的結構化資料也就是資料庫中的資料。 使用sql語句進行查詢,很快得到結果 2. 為什麼資料庫搜尋很容易? * 因為資料庫中的資料儲存是有規律的,有行有列而且資料格式、資料長度都是固定的。 * [簡單,速度快] 3. 非結構化資料查詢方法: 1. 順序掃描法: * 所謂的順序掃描,比如要找內容包含一個字串,就是一個文件一個文件的看,如果此文件包含此字串則為我們要找的檔案,藉著看下一個檔案,直到掃描所有的檔案。利用windows的搜尋也可以搜尋檔案內容,只是相當的慢。 2. 全文檢索 [把非結構化資料變成結構化資料]: 1. 概述: * 將結構化資料中的一部分資訊提取出來,重新組織,使其變得有一定結構,然後對此結構進行搜尋,從而達到搜尋相對較快的目的。 2. 索引: * 這部分從非結構化資料中提取出的然後重新組織的資訊,我們稱之為索引。 3. 分析: * 先根據空格進行字串拆分,得到一個單詞列表,基於單詞列表建立一個索引。然後查詢索引,根據單詞和文件的對應關係找到文件列表,這個過程就叫做全文檢索。 * 查詢的時候,先查詢索引表,通過關鍵字與座標對應找到該關鍵字的座標,再通過座標查詢存有關鍵字的表,速度很快。 * 樹形結構; 4. 總結: * 這種先建立索引然後查詢索引的過程就叫做全文檢索。 * 索引一次建立,多次使用,表現為每次查詢速度很快; * 以id為主鍵,[唯一]使用主鍵作為查詢條件速度很快。 5. 注意: 1. 並不是所有的表都需要建立索引,尤其是有增刪改; 2. 只查詢的資料可以用來建立索引; 4. 如何實現全文檢索: * 可以使用 Lucene 實現全文檢索。Lucene 是 apache 下的一個開放原始碼的全文檢索引擎工具包。提供了完整的查詢引擎和索引引擎,部分文字分析引擎。Lucene 的目的是為軟體開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能。 5. 應用場景: * 對於資料量大、資料結構不固定的資料可採用全文檢索方式搜尋,比如百度、Google 等搜尋引擎、論壇站內搜尋、電商網站站內搜尋等。 * 舉例: 1. 搜尋引擎: 百度,360搜尋,谷歌,搜狗 2. 站內搜尋: 論壇搜尋,微博,文章搜尋 3. 電商搜尋: 淘寶搜尋,京東搜尋 6. 種類: 1. lucene * 它是已經被淘汰的全文檢索技術,學習它是為了學習全文檢索原理; 2. ElasticSearch * 它在學成線上專案中有講解,它是最新的技術,使用率日益增加; 3. solr * 它在品優購專案中有講解
lucene
1. 什麼是Lucene? * Lucene是一個基於java開發全文檢索工具包。 2. Lucene實現全文檢索的流程 1. 建立索引 1. 獲得文件 * 原始文件:要基於那些資料來進行搜尋,那麼這些資料就是原始文件。 * 原始文件是指要索引和搜尋的內容。原始內容包括網際網路上的網頁、資料庫中的資料、磁碟上的檔案等。 * 搜尋引擎:使用爬蟲獲得原始文件。 * 目的:資訊採集的目的是為了對原始內容進行索引。 * 站內搜尋:資料庫中的資料。 * 我們要獲取磁碟上檔案的內容,可以通過檔案流來讀取文字檔案的內容,對於pdf、doc、xls 等檔案可通過第三方提供的解析工具讀取檔案內容,比如 Apache POI 讀取 doc和 xls 的檔案內容。 * 案例:直接使用io流讀取磁碟上的檔案,直接獲得原始文件 2. 構建文件物件: 1. 獲取原始內容的目的是為了索引,在索引前需要將原始內容建成文件(Document),文件中包括一個一個的域(Field),域中儲存內容。 2. 這裡我們可以將磁碟上的一個檔案當成一個document,Document中包括一些Filed(file_name檔名稱,file_path檔案路徑,file_size檔案大小,file_content檔案內容); 3. 域中儲存就是原始文件資料 * 域的名稱 * 域的值 4. 注意:每個Document可以有多個Field,同一個Document可以有相同的Field(域名和域值都相同),每個文件都有一個唯一的編號,就是文件id; 3. 分析文件: 1. 就是分詞的過程 1. 根據空格進行字串拆分,得到一個單詞列表 2. 把單詞統一轉換為小寫 3. 去除標點符號 4. 去除停用詞 * 停用詞: 無意義的詞; * 每個關鍵詞都封裝成一個Term物件中 * Term中包含兩部分內容 * 關鍵詞所在的域 * 關鍵詞本身 * 不同的域中拆分出來的相同的關鍵詞是不同的Term。 4. 建立索引: 1. 基於關鍵詞列表建立一個索引。儲存到索引庫中; 2. 索引庫中: 1. 索引 2. document物件 3. 關鍵詞和文件的對應關係 3. 倒排索引結構:通過詞語找文件 2. 查詢索引: 1. 使用者查詢介面 1. 使用者輸入查詢條件的地方 2. 例如:百度的搜尋框 2. 把關鍵詞封裝成一個查詢物件; 1. 要查詢的域 2. 要搜尋的關鍵詞 3. 執行查詢 1. 根據要查詢的關鍵詞到對應的域上進行搜尋。 2. 找到關鍵詞,根據關鍵詞找到對應的文件。 4. 渲染結果 1. 根據文件的id找到文件物件 2. 對關鍵詞進行高亮顯示 3. 分頁處理 4. 最終展示給使用者看 3. 過程: 1. 索引過程:對搜尋的原始內容進行索引構建一個索引庫,索引過程包括: * 確定原始內容即要搜尋的內容->採集文件->建立文件->分析文件->索引文件 2. 搜尋過程:從索引庫中搜索內容,搜尋過程包括: * 使用者通過搜尋介面->建立查詢->執行搜尋,從索引庫搜尋->渲染搜尋結果
入門程式
1. 建立索引 * 環境: 1. 下載 * http://lucene.apache.org/ * 最低要求:JDK1.8 2. 解壓縮 3. 檔案含義: 1. core資料夾: 核心 2. analysis->lucene-analyzers-common-7.4.0.jar : 分析 4. 搭建: 1. 建立一個java工程 2. 新增jar: * lucene-analyzers-common-7.4.0.jar * lucene-core-7.4.0.jar * commons-io.jar 5. 開始環境搭建: 1. 建立一個java空專案 * 建立一個Modules模組,選擇"JAVA" ; [JDK必須在1.8以上] 2. 新建一個目錄:lib * 新增上面的jar包 * Dependencies->新增匯入依賴 * 步驟: 1. 建立一個Director物件,指定索引庫儲存的位置 2. 基於Directory物件建立一個IndexWriter物件 3. 讀取磁碟上的檔案,對應每個檔案建立一個文件物件 4. 向文件物件中新增域 5. 把文件物件寫入索引庫 6. 關閉indexWriter物件 2. 開始 1. 建立一個LuceneFirst類: public class LuceneFirst { @Test public void createIndex() throws Exception { //1、建立一個Director物件,指定索引庫儲存的位置。 //把索引庫儲存在記憶體中 //Directory directory = new RAMDirectory(); //把索引庫儲存在磁碟 Directory directory = FSDirectory.open(new File("C:\\temp\\index").toPath()); //2、基於Directory物件建立一個IndexWriter物件 IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig()); //3、讀取磁碟上的檔案,對應每個檔案建立一個文件物件。 File dir = new File("C:\\A0.lucene2018\\05.參考資料\\searchsource"); File[] files = dir.listFiles(); for (File f : files) { //取檔名 String fileName = f.getName(); //檔案的路徑 String filePath = f.getPath(); //檔案的內容 String fileContent = FileUtils.readFileToString(f, "utf-8"); //檔案的大小 long fileSize = FileUtils.sizeOf(f); //建立Field //引數1:域的名稱,引數2:域的內容,引數3:是否儲存 Field fieldName = new TextField("name", fileName, Field.Store.YES); //Field fieldPath = new TextField("path", filePath, Field.Store.YES); Field fieldPath = new StoredField("path", filePath); Field fieldContent = new TextField("content", fileContent, Field.Store.YES); //Field fieldSize = new TextField("size", fileSize + "", Field.Store.YES); Field fieldSizeValue = new LongPoint("size", fileSize); Field fieldSizeStore = new StoredField("size", fileSize); //建立文件物件 Document document = new Document(); //向文件物件中新增域 document.add(fieldName); document.add(fieldPath); document.add(fieldContent); //document.add(fieldSize); document.add(fieldSizeValue); document.add(fieldSizeStore); //5、把文件物件寫入索引庫 indexWriter.addDocument(document); } //6、關閉indexwriter物件 indexWriter.close(); } 2. 使用luke檢視索引庫中的內容 1. 資料->luke-javafx-7.4.0-luke-reale 2. 解壓後雙擊開啟 [最低執行版本:jdk1.9] 3. 在name中不同類中的分詞出來的詞即便是一樣的,其型別還是不一樣。比如標題名叫apache和內容中包含apache,這裡的兩個apache不一樣; 4. 查詢: 1. *:* ,它表示查詢全部; 5. 使用此工具用於測試和檢視,實際工作中我們還是需要用程式來完成這些操作,接下來繼續我們的入門程式; 3. 查詢索引庫 1. 步驟: 1. 建立一個Director物件,指定索引庫的位置; 2. 建立一個IndexReader物件 3. 建立一個IndexSearcher物件,構造方法中的引數就是indexReader物件。 4. 建立一個Query物件,TermQuery [它的意思就是根據關鍵詞查詢] 5. 執行查詢,得到一個TopDocs物件 6. 取查詢結果的總記錄數。 7. 取文件列表 8. 列印文件中的內容 9. 關閉indexReader物件 2. 實現: * 繼續在LuceneFirst類中寫: * public class LuceneFirst { * .... @Test public void searchIndex() throws Exception { //1、建立一個Director物件,指定索引庫的位置 Directory directory = FSDirectory.open(new File("C:\\temp\\index").toPath()); //2、建立一個IndexReader物件 IndexReader indexReader = DirectoryReader.open(directory); //3、建立一個IndexSearcher物件,構造方法中的引數indexReader物件。 IndexSearcher indexSearcher = new IndexSearcher(indexReader); //4、建立一個Query物件,TermQuery Query query = new TermQuery(new Term("name", "spring")); //5、執行查詢,得到一個TopDocs物件 //引數1:查詢物件 引數2:查詢結果返回的最大記錄數 TopDocs topDocs = indexSearcher.search(query, 10); //6、取查詢結果的總記錄數 System.out.println("查詢總記錄數:" + topDocs.totalHits); //7、取文件列表 ScoreDoc[] scoreDocs = topDocs.scoreDocs; //8、列印文件中的內容 for (ScoreDoc doc : scoreDocs) { //取文件id int docId = doc.doc; //根據id取文件物件 Document document = indexSearcher.doc(docId); System.out.println(document.get("name")); System.out.println(document.get("path")); System.out.println(document.get("size")); //System.out.println(document.get("content")); System.out.println("-----------------寂寞的分割線"); } //9、關閉IndexReader物件 indexReader.close(); } 4.分析器 1. 預設使用的數標準分析器StandardAnalyzer 2. 檢視分析器的分析效果 1. 使用Analyzer物件的tokenStream方法返回一個TokenStream物件,詞物件中包含了最終分詞結果。 2. 實現步驟: 1. 建立一個Analyzer物件,StandarAnalyzer物件 2. 使用分析器物件的tokenStream方法獲得一個TokenStream物件 3. 向TokenStream物件中設定一個引用,相當於數一個指標; 4. 呼叫TokenStream物件的rest方法,如果不呼叫拋異常 5. 使用while迴圈遍歷TokenStream物件 6. 關閉TokenStream物件 3. 實現: * 繼續在LuceneFirst類中寫: * public class LuceneFirst { * .... * Test @Test public void testTokenStream() throws Exception { //1)建立一個Analyzer物件,StandardAnalyzer物件 Analyzer analyzer = new StandardAnalyzer(); //2)使用分析器物件的tokenStream方法獲得一個TokenStream物件 TokenStream tokenStream = analyzer.tokenStream("", "2017年12月14日 - 傳智播客Lucene概述公安局Lucene是一款高效能的、可擴充套件的資訊檢索(IR)工具庫。資訊檢索是指文件搜尋、文件內資訊搜尋或者文件相關的元資料搜尋等操作。"); //3)向TokenStream物件中設定一個引用,相當於數一個指標 CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class); //4)呼叫TokenStream物件的rest方法。如果不呼叫拋異常 tokenStream.reset(); //5)使用while迴圈遍歷TokenStream物件 while(tokenStream.incrementToken()) { System.out.println(charTermAttribute.toString()); } //6)關閉TokenStream物件 tokenStream.close();
中文分析器
1. 標準分析器的缺點:
1. 預設分詞器是美國的一個程式設計師開發的,分詞為英文的時候,以空格為分隔點,然後拆分成各個單詞,中文只支援一個漢字一個關鍵字,準確度並不高,所以不適用;所以我們必須要使用中文分析器對其進行分析。
2. IK分詞器
* 在詞語被分詞的時候,它被分詞成幾個部分
* 在搜尋的時候,分詞器拿到value值,並將其也分成幾個部分
* 然後幾個部分找幾個部分,按照關聯度高地進行排列;
3. IKAnalyze的使用方法
1. 把IKAnalyzer的jar包新增到工程中
2. 把配置檔案和擴充套件詞典新增到工程的classpath下;
3. 注意:
* 擴充套件詞典嚴禁使用windows記事本編輯,否則會使擴充套件詞典不生效;
* 必須保證編寫的格式是utf-8 [windows記事本的utf-8不是標準的utf-8,它是utf-8+BOM,不是純的utf-8];
* 可以使用notepad++等編輯.
4. 擴充套件詞典[hotword.dic]: 新增一些日新月異的新詞/公司名稱等...
5. 停用詞詞典[stopword.dic]: 無意義的詞/敏感詞等
4. 演示中文分析器的使用方法:
1. 將IK-Analyzer...jar包匯入
2. 將配置檔案,擴充套件詞典,停用詞典放入該包下:com.itheima.lucene
3. 實現:
* 繼續在LuceneFirst類中寫:
* public class LuceneFirst {
* ....
* Test
@Test
public void testTokenStream() throws Exception {
//1)建立一個Analyzer物件,StandardAnalyzer物件
// Analyzer analyzer = new StandardAnalyzer(); 英文分析器
Analyzer analyzer = new IKAnalyzer(); 中文分析器
//2)使用分析器物件的tokenStream方法獲得一個TokenStream物件
TokenStream tokenStream = analyzer.tokenStream("", "2017年12月14日 - 傳智播客Lucene概述公安局Lucene是一款高效能的、可擴充套件的資訊檢索(IR)工具庫。資訊檢索是指文件搜尋、文件內資訊搜尋或者文件相關的元資料搜尋等操作。");
//3)向TokenStream物件中設定一個引用,相當於數一個指標
CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
//4)呼叫TokenStream物件的rest方法。如果不呼叫拋異常
tokenStream.reset();
//5)使用while迴圈遍歷TokenStream物件
while(tokenStream.incrementToken()) {
System.out.println(charTermAttribute.toString());
}
//6)關閉TokenStream物件
tokenStream.close();
5. 在程式碼中使用分析器
1. 實現:
public class LuceneFirst {
@Test
public void createIndex() throws Exception {
//1、建立一個Director物件,指定索引庫儲存的位置。
//把索引庫儲存在記憶體中
//Directory directory = new RAMDirectory();
//把索引庫儲存在磁碟
Directory directory = FSDirectory.open(new File("C:\\temp\\index").toPath());
//2、基於Directory物件建立一個IndexWriter物件
IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory, config);
//3、讀取磁碟上的檔案,對應每個檔案建立一個文件物件。
File dir = new File("C:\\A0.lucene2018\\05.參考資料\\searchsource");
File[] files = dir.listFiles();
for (File f :
files) {
//取檔名
String fileName = f.getName();
//檔案的路徑
String filePath = f.getPath();
//檔案的內容
String fileContent = FileUtils.readFileToString(f, "utf-8");
//檔案的大小
long fileSize = FileUtils.sizeOf(f);
//建立Field
//引數1:域的名稱,引數2:域的內容,引數3:是否儲存
Field fieldName = new TextField("name", fileName, Field.Store.YES);
//Field fieldPath = new TextField("path", filePath, Field.Store.YES);
Field fieldPath = new StoredField("path", filePath);
Field fieldContent = new TextField("content", fileContent, Field.Store.YES);
//Field fieldSize = new TextField("size", fileSize + "", Field.Store.YES);
Field fieldSizeValue = new LongPoint("size", fileSize);
Field fieldSizeStore = new StoredField("size", fileSize);
//建立文件物件
Document document = new Document();
//向文件物件中新增域
document.add(fieldName);
document.add(fieldPath);
document.add(fieldContent);
//document.add(fieldSize);
document.add(fieldSizeValue);
document.add(fieldSizeStore);
//5、把文件物件寫入索引庫
indexWriter.addDocument(document);
}
//6、關閉indexwriter物件
indexWriter.close();
}
索引庫維護
1. 域的型別:
1. StringField(FieldName,FieldValue,Store.YES):
* 這個用來構造Field,不分析,會將整個儲存在索引中,比如姓名,身份證,訂單編號等不需要分詞;
* 不分詞,建索引,儲存/不儲存
* [有些可能不需要儲存,注意:不儲存不代表不建索引庫,搜整條資料依然可以找到它]
2. LongPoint(String name,long...Point):
* 可以使用LongPoint/IntPoint等型別儲存資料型別的資料
* 分詞,建索引,不儲存
* 如果需要儲存資料還需要使用StoreField
3. StoreField(FieldName,FieldValue):
* 這個Field用來構建不同型別Field
* 不分析,不索引,但要Field要儲存在文件中
4. TextField(FieldName,FieldValue,Store.No)或TextField(FieldName,reader)
* 如果是一個Reader,lucene猜測內容比較多,會採用Unstored的策略;
5. 注意:
* 如果沒有建索引,有儲存,那麼可以查詢到該內容,但是內容中沒有該關鍵詞資料;
2. 新增文件,改造:
Field fieldName = new TextField("name", fileName, Field.Store.YES);
Field fieldPath = new StoredField("path", filePath);
Field fieldContent = new TextField("content", fileContent, Field.Store.YES);
Field fieldSizeValue = new LongPoint("size", fileSize);
Field fieldSizeStore = new StoredField("size", fileSize);
新增域:
document.add(fieldName);
document.add(fieldPath);
document.add(fieldContent);
document.add(fieldSizeValue);
document.add(fieldSizeStore);
3. 索引庫維護-新增
1. 新建一個類:IndexManger
* public class IndexManger{
*
// 新增索引
@Test
public void addDocument() throws Exception {
// 索引庫存放路徑
Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
// 建立一個 indexwriter 物件
IndexWriter indexWriter = new IndexWriter(directory, config);
// 建立一個 Document 物件
Document document = new Document();
// 向 document 物件中新增域。
// 不同的 document 可以有不同的域,同一個 document 可以有相同的域。
document.add(new TextField("filename", " 新新增的文件", Field.Store.YES));
document.add(new TextField("content", " 新新增的文件的內容", Field.Store.NO));
//LongPoint 建立索引
document.add(new LongPoint("size", 1000l));
//StoreField 儲存資料
document.add(new StoredField("size", 1000l));
// 不需要建立索引的就使用 StoreField 儲存
document.add(new StoredField("path", "d:/temp/1.txt"));
// 新增文件到索引庫
indexWriter.addDocument(document);
// 關閉 indexwriter
indexWriter.close();
}
4. 索引庫維護-刪除
* 刪除文件有兩種方式:
1. 刪除全部
2. 根據查詢,修改
* 示例:
* 繼續上面的新增類...
* @Test
* public void deleteAllDocument() throws Exception{
* //刪除全部文件
* indexWriter.deleteAll();
* //indexWriter.close();
* }
* @Test
* public void deleteDocumentByQuery() throws Exception{
* //如果刪除了索引庫,需要先建立索引庫再查詢
* indexWriter.deleteDocuments(new Term("name","apache"))
* indexWriter.close();
* }
5. 索引庫維護-更新
* 特點:先刪除原來的,然後再進行新增操作;
* 示例:
* public void updateDocument() throws Exception{
* //建立一個新的文件物件
* Document document =new Document();
* //向文件物件中新增域
* document.add(new TextField("name","更新之後的文件",Field.Store.YES));
* document.add(new TextField("name1","更新之後的文件1",Field.Store.YES));
* document.add(new TextField("name2","更新之後的文件2",Field.Store.YES));
* //更新索引庫
* indexWriter.updateDocument(new Term("name","spring"),document);
* indexWriter.close();
* }
6. 數值範圍查詢
1. TermQuery:
1. 根據關鍵詞查詢,需要指定要查詢的域及要查詢的關鍵詞;
2. 範圍進行查詢
2. 使用QueryPaser進行查詢
1. 可以對要查詢的內容先進行分詞,然後基於分詞的結果進行查詢;
2. 需要匯入jar包:lucene-queryparser-7.4.0.jar
3. 範圍查詢和QueryParser查詢的程式碼整合如下:
public class SearchIndex {
private IndexReader indexReader;
private IndexSearcher indexSearcher;
@Before
public void init() throws Exception {
indexReader = DirectoryReader.open(FSDirectory.open(new File("C:\\temp\\index").toPath()));
indexSearcher = new IndexSearcher(indexReader);
}
@Test
public void testRangeQuery() throws Exception {
//建立一個Query物件
Query query = LongPoint.newRangeQuery("size", 0l, 100l);
printResult(query);
}
private void printResult(Query query) throws Exception {
//執行查詢
TopDocs topDocs = indexSearcher.search(query, 10);
System.out.println("總記錄數:" + topDocs.totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc doc:scoreDocs){
//取文件id
int docId = doc.doc;
//根據id取文件物件
Document document = indexSearcher.doc(docId);
System.out.println(document.get("name"));
System.out.println(document.get("path"));
System.out.println(document.get("size"));
//System.out.println(document.get("content"));
System.out.println("-----------------寂寞的分割線");
}
indexReader.close();
}
@Test
public void testQueryParser() throws Exception {
//建立一個QueryPaser物件,兩個引數
QueryParser queryParser = new QueryParser("name", new IKAnalyzer());
//引數1:預設搜尋域,引數2:分析器物件
//使用QueryPaser物件建立一個Query物件
Query query = queryParser.parse("lucene是一個Java開發的全文檢索工具包");
//執行查詢
printResult(query);
}
}