結構化數據:指的是格式固定、長度固定、數據類型固定的數據,例如數據庫中的數據。html
非結構化數據:指的是格式不固定、長度不固定、數據類型不固定的數據,例如 word 文檔、pdf 文檔、郵件、html。java
結構化數據的查詢:像數據庫中的數據咱們能夠經過 SQL 語句來進行查詢,簡單且速度快。git
非結構化數據的查詢:以「從多個文本文件中查詢出包含 spring 單詞的文件」爲例,咱們能夠經過一個一個打開經過目測瀏覽文本內容進行查找,也能夠經過將文檔讀到內存中,依次匹配字符串進行查找,再或者能夠將非結構化數據轉換爲結構化數據,如將其保存到數據庫中。github
先建立索引,而後經過索引查找的過程就叫作全文檢索。spring
有搜索的地方就可使用到全文檢索技術。數據庫
Lucene 是一個基於 Java 開發的全文檢索工具包。apache
原始文檔:要基於哪些數據來進行搜索,那麼這些數據就是原始文檔。mybatis
搜索引擎:使用爬蟲得到原始文檔。ide
站內搜索:數據庫中的數據。工具
案例:直接使用 IO 流讀取磁盤上的文件。
對應的每一個原始文檔就是一個 Document 對象,每一個 Document 對象包含多個域(Field),域中保存的就是原始文檔數據(域的名稱及域的值),每一個文檔對象都有一個惟一的編號,就是文檔 id。
就是分詞的過程。如:
a) 根據空格對字符串進行拆分,獲得一個單詞列表。
b) 把單詞統一轉換成小寫。
c) 去除標點符號。
d) 去除停用詞(無心義的詞)。
e) 最後將拆分出來的每個關鍵詞封裝成一個 Term 對象(Term 中包含兩部份內容,一是關鍵詞所在的域,二是關鍵詞自己,不一樣的域中拆分出來的相同的關鍵詞是不一樣的 Term)。
基於關鍵詞列表建立一個索引保存到索引庫中,索引庫中包含了索引、Document 對象、關鍵詞和文檔的對應關係。
其實就是用戶輸入查詢條件的地方,如百度的搜索框。
根據查詢的關鍵詞對象到對應的域上搜索,找到關鍵詞,即也能經過關鍵詞與文檔的對應關係找到對應的文檔。
根據文檔的 id 找到文檔對象,對關鍵詞進行高亮顯示、分頁處理等操做,最終顯示給用戶。
一、官網下載 Lucene:http://lucene.apache.org。
二、最低要求 jdk1.8。
三、依賴 jar 以下:
四、在 E:/temp/searchsouce 有以下測試文件:
do you like spring
do you like show you
do you like spring mybatis
do you like spring mybatisdeawdffeef show eyour resource
import org.apache.commons.io.FileUtils; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.TextField; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.junit.Test; import java.io.File; public class LuceneFirst { @Test public void createIndex() throws Exception{ //一、建立一個 Directory 對象,指定索引庫保存的位置。 // 把索引庫保存在內存中 // Directory directory = new RAMDirectory(); // 把索引庫保存在磁盤中 Directory directory = FSDirectory.open(new File("E:/temp/index").toPath()); //二、基於 Directory 對象建立一個 IndexWriter 對象。 IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig()); //三、讀取磁盤上的文件,對應每一個文件來建立一個文檔(Document)對象。 File dir = new File("E:/temp/searchsouce"); File[] files = dir.listFiles(); for (File file : files) { // 取文件名 String fileName = file.getName(); // 文件路徑 String filePath = file.getPath(); // 文件內容 String fileContent = FileUtils.readFileToString(file, "utf-8"); // 文件大小 long fileSize = FileUtils.sizeOf(file); // 建立 field // 參數1:域名稱 參數2:域的內容 參數3:是否保存 Field nameField = new TextField("name", fileName, Field.Store.YES); Field pathField = new TextField("path", filePath, Field.Store.YES); Field contentField = new TextField("content", fileContent, Field.Store.YES); Field sizeField = new TextField("size", fileSize + "", Field.Store.YES); // 建立文檔對象 Document document = new Document(); //四、向文檔對象中添加域(Field)。 document.add(nameField); document.add(pathField); document.add(contentField); document.add(sizeField); //五、把文檔對象寫入索引庫。 indexWriter.addDocument(document); } //六、關閉 IndexWriter 對象。 indexWriter.close(); } }
執行完上述單元測試後,在 E:/temp/index 下就會建立以下索引文件:
import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; import org.apache.lucene.search.*; import org.apache.lucene.store.FSDirectory; import org.junit.Test; import java.io.File; public class LuceneFirst { @Test public void searchIndex() throws Exception{ // 一、建立一個 Directory 對象,指定索引庫位置。 FSDirectory directory = FSDirectory.open(new File("E:/temp/index").toPath()); // 二、建立一個 IndexReader 對象。 IndexReader indexReader = DirectoryReader.open(directory); // 三、建立一個 IndexSearcher 對象,構造參數爲 IndexReader 對象。 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // 四、建立一個 Query 對象(TermQuery),在 content 域中查詢 spring 關鍵詞。 Query query = new TermQuery(new Term("content", "spring")); // 五、執行查詢,獲得一個 TopDocs 對象。 // 參數1:查詢對象 參數2:返回的記錄最大條數 TopDocs topDocs = indexSearcher.search(query, 10); // 六、取查詢結果的總記錄數。 System.out.println(topDocs.totalHits); // 七、取文檔列表。 ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 取文檔 id int id = scoreDoc.doc; // 根據 id 取文檔對象 Document document = indexSearcher.doc(id); // 八、打印文檔的內容。 System.out.println(document.get("name")); System.out.println(document.get("path")); System.out.println(document.get("content")); System.out.println(document.get("size")); } // 九、關閉 IndexReader 對象 indexReader.close(); } }
控制檯輸出:
若是咱們想要查看建立好的索引庫內容,能夠經過一個工具——luke 查看,下載地址 https://github.com/DmitryKey/luke/releases。
下載解壓後經過 luke.bat 批處理文件打開以下可視化界面:
在建立索引的代碼中建立 IndexWriter 對象時傳入的了 IndexWriterConfig 對象:
new IndexWriter(directory, new IndexWriterConfig());
在該對象中其實就建立了默認的分析器,看它的構造方法:
public IndexWriterConfig() { this(new StandardAnalyzer()); }
即默認的分析器就是 StandardAnalyzer 。
使用 Analyzer 對象的 tokenStream() 方法返回一個 TokenStream 對象,該對象中包含了最終的分詞結果。
import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.junit.Test; public class AnalyzerTest { @Test public void test() throws Exception { // 一、建立一個 Analyzer 對象(StandardAnalyzer)。 Analyzer analyzer = new StandardAnalyzer(); // 二、使用分析器對象的 tokenStream() 方法獲得一個 TokenStream 對象。 TokenStream tokenStream = analyzer.tokenStream("", "do you like spring mybatis"); // 三、向 TokenStream 對象中設置一個引用,至關於一個指針。 CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class); // 四、調用 TokenStream 對象的 reset() ,若是不調用則會拋異常。 tokenStream.reset(); // 五、遍歷 TokenStream 對象。 while (tokenStream.incrementToken()){ System.out.println(charTermAttribute.toString()); } // 六、關閉 TokenStream 對象。 tokenStream.close(); } }
執行上述單元測試控制檯輸出以下:
即默認的 StandardAnalyzer 分析器是按空格分詞,最終獲得的關鍵詞列表就是分析結果。(它是不支持中文的,當要分析的內容包含中文時,會將每個中文字符當作一個關鍵詞。)
咱們已經知道默認的分析器是不支持中文的,因此咱們可使用第三方提供的支持中文分析的分析器工具——IK中文分析器,點擊下載(提取碼:t9yg)。
在工程中加入下載下來的 jar 包,在 classpath 下添加相應配置文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>IK Analyzer 擴展配置</comment> <!--用戶能夠在這裏配置本身的擴展字典 --> <!--<entry key="ext_dict">ext.dic;</entry>--> <!--用戶能夠在這裏配置本身的擴展中止詞字典--> <entry key="ext_stopwords">stopword.dic;</entry> </properties>
a an and are as at be but by for if in into is it no not of on or such that the their then there these they this to was will with
而後只須要將原來的分析器實例( StandardAnalyzer )替換爲用 IK 分析器( IKAnalyzer )的實例便可,以下:
import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.junit.Test; import org.wltea.analyzer.lucene.IKAnalyzer; public class IKAnalyzerTest { @Test public void test() throws Exception{ // 一、建立一個 Analyzer 對象(StandardAnalyzer)。 Analyzer analyzer = new IKAnalyzer(); // 二、使用分析器對象的 tokenStream() 方法獲得一個 TokenStream 對象。 TokenStream tokenStream = analyzer.tokenStream("", "小張今天很開心"); // 三、向 TokenStream 對象中設置一個引用,至關於一個指針。 CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class); // 四、調用 TokenStream 對象的 reset() ,若是不調用則會拋異常。 tokenStream.reset(); // 五、遍歷 TokenStream 對象。 while (tokenStream.incrementToken()){ System.out.println(charTermAttribute.toString()); } // 六、關閉 TokenStream 對象。 tokenStream.close(); } }
執行上述單元測試,控制檯輸出結果以下:
回頭看一下 IKAnalyzer.cfg.xml 配置文件,在其中配置了兩個 entry 節點,它們分別有不一樣的 key 屬性值。
key 名稱爲 ext_dict 對應的節點用來指定一個擴展詞文件路徑,該擴展詞文件中能夠定義一些咱們本身但願識別爲關鍵詞的詞語,以換行隔開便可;
key 名稱爲 ext_stopwords 對應的節點用來指定一個停用詞文件路徑,該停用詞文件中能夠定義一些咱們指定的無心義詞語。
若是想要在建立索引庫的時候使用 IKAnalyzer 進行分詞,那麼只須要在建立 IndexWriterConfig 實例時指定其構造參數爲 IKAnalyzer 的實例便可,如:
IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new IKAnalyzer()));
Field 域通常有三個屬性:
如:商品名稱、商品簡介需分析後進行索引,而訂單號和身份證號不用分析但也須要索引,由於它們都有可能做爲查詢條件。
如商品名稱、訂單號等凡是未來要從 Document 中獲取的 Field 都要存儲。
Field 域的經常使用實現有以下幾種:
Field | 存儲數據類型 | 是否分析(Analyzed) | 是否索引(Indexed) | 是否存儲(Stored) | 說明 |
---|---|---|---|---|---|
StringField(FieldName,FiledValue,Store) | 字符串類型 | N | Y | Y或N | 這個 Field 用來構建一個字符串 Field,可是不會進行分析,會將整個串直接索引,一般用來存儲如身份證號、姓名等字段內容,是否存儲經過第三個參數指定 Store.YES 或 Store.NO 決定。 |
LongPoint(FieldName,FieldValue) | Long 類型 | Y | Y | N | 可使用 LongPoint、IntPoint 等類型存儲數值類型的數據來讓數值類型進行索引,但僅使用它是不能存儲數據的,要同時存儲數據須要搭配 StoredField 一塊兒使用。 |
StoredField(FieldName,FieldValue) | 重載構造方法,支持多種類型。 | N | N | Y | 這個 Field 用來構建不一樣類型的 Field,不分析、不索引,但會讓 Field 存儲在文檔中。 |
TextField(FieldName,FieldValue,Store) | 字符串或流 | Y | Y | Y或N | 若是是一個 Reader,Lucene 會猜想內容比較多,採用 UnStored 即不存儲的策略。 |
文檔管理指的就是針對索引庫中文檔的 CRUD 操做,基本的操做方式大體以下:
import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StoredField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.Term; import org.apache.lucene.store.FSDirectory; import org.junit.Before; import org.junit.Test; import org.wltea.analyzer.lucene.IKAnalyzer; import java.io.File; public class DocumentManagerTest { IndexWriter indexWriter; @Before public void init() throws Exception { // 建立一個 IndexWriter 對象,使用 IKAnalyzer 做爲分析器 indexWriter = new IndexWriter(FSDirectory.open(new File("E:/temp/index").toPath()), new IndexWriterConfig(new IKAnalyzer())); } /** * 添加文檔到索引庫 */ @Test public void addDocument() throws Exception { // 建立一個 Document 對象 Document document = new Document(); // 向 Document 對象中添加域 document.add(new TextField("name", "新添加的文件名", Field.Store.YES)); document.add(new TextField("content", "新添加的文件內容", Field.Store.YES)); document.add(new StoredField("path", "E:/temp/searchsource/test1.txt")); // 把文檔寫入索引庫 indexWriter.addDocument(document); // 釋放 IndexWriter indexWriter.close(); } /** * 刪除所有文檔 */ @Test public void deleteAllDocument() throws Exception { // 刪除所有文檔 indexWriter.deleteAll(); // 釋放 IndexWriter indexWriter.close(); } /** * 根據查詢對象刪除指定文檔 */ @Test public void deleteDocumentByQuery() throws Exception { // 刪除 content 域中包含關鍵詞 Spring 的文檔 indexWriter.deleteDocuments(new Term("content", "Spring")); // 釋放 IndexWriter indexWriter.close(); } /** * 更新文檔 實際上就是先刪除再新增 */ @Test public void updateDocument() throws Exception{ // 建立一個新的文檔 Document document = new Document(); // 向 Document 對象中添加域 document.add(new TextField("name", "要更新的文件名", Field.Store.YES)); document.add(new TextField("content", "要更新的文件內容", Field.Store.YES)); document.add(new StoredField("path", "E:/temp/searchsource/test2.txt")); // 更新操做 其實是先刪除 name 域中包含關鍵詞 Spring 的文檔,再添加 document 文檔到索引庫 indexWriter.updateDocument(new Term("name","Spring"),document); // 釋放 IndexWriter indexWriter.close(); } }
索引庫的經常使用查詢方式有以下幾種:
一、使用 Query 的子類:
import org.apache.lucene.document.Document; import org.apache.lucene.document.LongPoint; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.FSDirectory; import org.junit.Before; import org.junit.Test; import java.io.File; public class RangeQueryTest { IndexSearcher indexSearcher; IndexReader indexReader; @Before public void init() throws Exception { // 一、建立一個 Directory 對象,指定索引庫位置。 FSDirectory directory = FSDirectory.open(new File("E:/temp/index").toPath()); // 二、建立一個 IndexReader 對象。 indexReader = DirectoryReader.open(directory); // 三、建立一個 IndexSearcher 對象,構造參數爲 IndexReader 對象。 indexSearcher = new IndexSearcher(indexReader); } @Test public void test() throws Exception { // 四、建立查詢對象 // 參數1:要查詢的域名稱 參數 2:查詢內容值的最小值 3:查詢內容值的最大值 Query query = LongPoint.newRangeQuery("size", 1, 1000); // 五、執行查詢,獲得一個 TopDocs 對象。 // 參數1:查詢對象 參數2:返回的記錄最大條數 TopDocs topDocs = indexSearcher.search(query, 10); // 六、取查詢結果的總記錄數。 System.out.println(topDocs.totalHits); // 七、取文檔列表。 ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 取文檔 id int id = scoreDoc.doc; // 根據 id 取文檔對象 Document document = indexSearcher.doc(id); // 八、打印文檔的內容。 System.out.println(document.get("name")); System.out.println(document.get("path")); System.out.println(document.get("content")); System.out.println(document.get("size")); } // 九、關閉 IndexReader 對象 indexReader.close(); } }
二、使用 QueryParser 進行查詢:
使用 QueryParser 其實是對要查詢的內容先分詞,而後基於分詞的結果進行查詢。要使用它須要再添加以下 jar 包:
lucene-queryparser-8.1.1.jar
import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.FSDirectory; import org.junit.Before; import org.junit.Test; import org.wltea.analyzer.lucene.IKAnalyzer; import java.io.File; public class QueryParserTest { IndexReader indexReader; IndexSearcher indexSearcher; @Before public void init() throws Exception { // 一、建立一個 Directory 對象,指定索引庫位置。 FSDirectory directory = FSDirectory.open(new File("E:\\temp\\index").toPath()); // 二、建立一個 IndexReader 對象。 indexReader = DirectoryReader.open(directory); // 三、建立一個 IndexSearcher 對象,構造參數爲 IndexReader 對象。 indexSearcher = new IndexSearcher(indexReader); } @Test public void test() throws Exception { // 建立一個 QueryParser 對象,參數1:要查詢的域名稱 參數2:要使用的分析器對象 QueryParser queryParser = new QueryParser("content", new IKAnalyzer()); // 使用 QueryParser 對象建立一個 Query 對象 Query query = queryParser.parse("spring is the season after winter and before summer"); // 執行查詢 // 五、執行查詢,獲得一個 TopDocs 對象。 // 參數1:查詢對象 參數2:返回的記錄最大條數 TopDocs topDocs = indexSearcher.search(query, 10); // 六、取查詢結果的總記錄數。 System.out.println(topDocs.totalHits); // 七、取文檔列表。 ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 取文檔 id int id = scoreDoc.doc; // 根據 id 取文檔對象 Document document = indexSearcher.doc(id); // 八、打印文檔的內容。 System.out.println(document.get("name")); System.out.println(document.get("path")); System.out.println(document.get("content")); System.out.println(document.get("size")); } // 九、關閉 IndexReader 對象 indexReader.close(); } }