公衆號閱讀
https://mp.weixin.qq.com/s/M3...css
[TOC]git
The Apache LuceneTM project develops open-source search software, including:
Lucene Core, our flagship sub-project, provides Java-based indexing and search technology, as well as spellchecking, hit highlighting and advanced analysis/tokenization capabilities.
lucene官網(http://lucene.apache.org/)github
Lucene是apache軟件基金會4 jakarta項目組的一個子項目,是一個開放源代碼的全文檢索引擎工具包,但它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文本分析引擎(英文與德文兩種西方語言)。Lucene的目的是爲軟件開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能,或者是以此爲基礎創建起完整的全文檢索引擎。Lucene是一套用於全文檢索和搜尋的開源程式庫,由Apache軟件基金會支持和提供。Lucene提供了一個簡單卻強大的應用程式接口,可以作全文索引和搜尋。在Java開發環境裏Lucene是一個成熟的免費開源工具。就其自己而言,Lucene是當前以及最近幾年最受歡迎的免費Java信息檢索程序庫。人們常常提到信息檢索程序庫,雖然與搜索引擎有關,但不該該將信息檢索程序庫與搜索引擎相混淆。
如今Lucene在互聯網行業的用的很是普遍,尤爲是大數據時代的今天,那麼根據本身的理解給你們簡單的介紹一下爲何要學習Lucene。
傳統的sql查詢方式,數據量過多時,數據庫的壓力就會變得很大,查詢速度會變得很是慢。咱們須要使用更好的解決方案來分擔數據庫的壓力。爲了解決數據庫壓力和速度的問題,咱們的數據庫就變成了索引庫,咱們使用Lucene的API的來操做服務器上的索引庫。這樣徹底和數據庫進行了隔離。
算法
如今咱們已經瞭解了Lucene。sql
總結:Lucene全文檢索就是對文檔中所有內容進行分詞,而後對全部單詞創建倒排索引的過程。數據庫
檢索數據須要咱們先分詞
,存入索引庫
。apache
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <lunece.version>4.10.2</lunece.version> </properties> <dependencies> <!-- 分詞器 --> <dependency> <groupId>com.janeluo</groupId> <artifactId>ikanalyzer</artifactId> <version>2012_u6</version> </dependency> <!-- lucene核心庫 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-core</artifactId> <version>${lunece.version}</version> </dependency> <!-- Lucene的查詢解析器 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>${lunece.version}</version> </dependency> <!-- lucene的默認分詞器庫 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>${lunece.version}</version> </dependency> <!-- lucene的高亮顯示 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-highlighter</artifactId> <version>${lunece.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies>
// 建立索引 @Test public void testCreate() throws Exception { // 1 建立文檔對象 Document document = new Document(); // 建立並添加字段信息。參數:字段的名稱、字段的值、是否存儲,這裏選Store.YES表明存儲到文檔列表。Store.NO表明不存儲 document.add(new StringField("id", "1", Store.YES)); // 這裏咱們title字段須要用TextField,即建立索引又會被分詞。StringField會建立索引,可是不會被分詞 document.add(new TextField("title", "谷歌地圖之父跳槽facebook", Store.YES)); // 2 索引目錄類,指定索引在硬盤中的位置 Directory directory = FSDirectory.open(new File("E:\\luceneTest")); // 3 建立分詞器對象 // Analyzer analyzer = new StandardAnalyzer(); Analyzer analyzer = new IKAnalyzer(); // 4 索引寫出工具的配置對象 IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer); // 是否清空索引庫;設置打開方式:OpenMode.APPEND // 會在索引庫的基礎上追加新索引。OpenMode.CREATE會先清空原來數據,再提交新的索引 conf.setOpenMode(OpenMode.CREATE); // 5 建立索引的寫出工具類。參數:索引的目錄和配置信息 IndexWriter indexWriter = new IndexWriter(directory, conf); // 6 把文檔交給IndexWriter indexWriter.addDocument(document); // 7 提交 indexWriter.commit(); // 8 關閉 indexWriter.close(); }
索引查看工具
啓動run.bat
數組
*** @Test public void testSearch() throws Exception { // 索引目錄對象 Directory directory = FSDirectory.open(new File("E:\\luceneTest")); // 索引讀取工具 IndexReader reader = DirectoryReader.open(directory); // 索引搜索工具 IndexSearcher searcher = new IndexSearcher(reader); // 建立查詢解析器,兩個參數:默認要查詢的字段的名稱,分詞器 QueryParser parser = new QueryParser("title", new IKAnalyzer()); // 建立查詢解析器,倆個參數:默認要查詢的字段名稱,分詞器 // MultiFieldQueryParser parser2 = new MultiFieldQueryParser(new // String[] { // "id", "title" }, new IKAnalyzer()); // Query query2 = parser2.parse("1"); // 建立查詢對象 Query query = parser.parse("谷歌之父"); // 搜索數據,兩個參數:查詢條件對象要查詢的最大結果條數 // 返回的結果是 按照匹配度排名得分前N名的文檔信息(包含查詢到的總條數信息、全部符合條件的文檔的編號信息)。 TopDocs topDocs = searcher.search(query, 10); // 獲取總條數 System.out.println("本次搜索共找到" + topDocs.totalHits + "條數據"); // 獲取得分文檔對象(ScoreDoc)數組.SocreDoc中包含:文檔的編號、文檔的得分 ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 取出文檔編號 int docID = scoreDoc.doc; // 根據編號去找文檔 Document doc = reader.document(docID); System.out.println("id: " + doc.get("id")); System.out.println("title: " + doc.get("title")); // 取出文檔得分 System.out.println("得分: " + scoreDoc.score); } }
注:代碼中加了必要註釋服務器
public void search(Query query) throws Exception { // 索引目錄對象 Directory directory = FSDirectory.open(new File("E:\\luceneTest")); // 索引讀取工具 IndexReader reader = DirectoryReader.open(directory); // 索引搜索工具 IndexSearcher searcher = new IndexSearcher(reader); // 搜索數據,兩個參數:查詢條件對象要查詢的最大結果條數 // 返回的結果是 按照匹配度排名得分前N名的文檔信息(包含查詢到的總條數信息、全部符合條件的文檔的編號信息)。 TopDocs topDocs = searcher.search(query, 10); // 獲取總條數 System.out.println("本次搜索共找到" + topDocs.totalHits + "條數據"); // 獲取得分文檔對象(ScoreDoc)數組.SocreDoc中包含:文檔的編號、文檔的得分 ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 取出文檔編號 int docID = scoreDoc.doc; // 根據編號去找文檔 Document doc = reader.document(docID); System.out.println("id: " + doc.get("id")); System.out.println("title: " + doc.get("title")); // 取出文檔得分 System.out.println("得分: " + scoreDoc.score); } }
注:普通詞條查詢架構
/* * 測試普通詞條查詢 注意:Term(詞條)是搜索的最小單位,不可再分詞。值必須是字符串! */ @Test public void testTermQuery() throws Exception { // 建立詞條查詢對象 Query query = new TermQuery(new Term("title", "谷歌地圖")); search(query); }
注:通配符查詢
/* * 測試通配符查詢 ? 能夠表明任意一個字符 * 能夠任意多個任意字符 */ @Test public void testWildCardQuery() throws Exception { // 建立查詢對象 Query query = new WildcardQuery(new Term("title", "*歌*")); search(query); }
注:模糊查詢;數組範圍查詢;布爾查詢
/* * 測試模糊查詢 */ @Test public void testFuzzyQuery() throws Exception { // 建立模糊查詢對象:容許用戶輸錯。可是要求錯誤的最大編輯距離不能超過2 // 編輯距離:一個單詞到另外一個單詞最少要修改的次數 facebool --> facebook 須要編輯1次,編輯距離就是1 // Query query = new FuzzyQuery(new Term("title","fscevool")); // 能夠手動指定編輯距離,可是參數必須在0~2之間 Query query = new FuzzyQuery(new Term("title", "facevool"), 2); search(query); } /*************************************************************** * 測試:數值範圍查詢 注意:數值範圍查詢,能夠用來對非String類型的ID進行精確的查找 */ @Test public void testNumericRangeQuery() throws Exception { // 數值範圍查詢對象,參數:字段名稱,最小值、最大值、是否包含最小值、是否包含最大值 Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true); search(query); } /***************************************************************** * 布爾查詢: 布爾查詢自己沒有查詢條件,能夠把其它查詢經過邏輯運算進行組合! 交集:Occur.MUST + Occur.MUST * 並集:Occur.SHOULD + Occur.SHOULD 非:Occur.MUST_NOT */ // @Test // public void testBooleanQuery() throws Exception { // // Query query1 = NumericRangeQuery.newLongRange("id", 1L, 3L, true, true); // Query query2 = NumericRangeQuery.newLongRange("id", 2L, 4L, true, true); // // 建立布爾查詢的對象 // BooleanQuery query = new BooleanQuery(); // // 組合其它查詢 // query.add(query1, BooleanClause.Occur.MUST_NOT); // query.add(query2, BooleanClause.Occur.SHOULD); // // search(query); // }
注:修改和刪除操做
/* * 測試:修改索引 注意: A:Lucene修改功能底層會先刪除,再把新的文檔添加。 * B:修改功能會根據Term進行匹配,全部匹配到的都會被刪除。這樣很差 C:所以,通常咱們修改時,都會根據一個惟一不重複字段進行匹配修改。例如ID * D:可是詞條搜索,要求ID必須是字符串。若是不是,這個方法就不能用。 * 若是ID是數值類型,咱們不能直接去修改。能夠先手動刪除deleteDocuments(數值範圍查詢鎖定ID),再添加。 */ @Test public void testUpdate() throws Exception { // 建立目錄對象 Directory directory = FSDirectory.open(new File("E:\\luceneTest")); // 建立配置對象 IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer()); // 建立索引寫出工具 IndexWriter writer = new IndexWriter(directory, conf); // 建立新的文檔數據 Document doc = new Document(); doc.add(new StringField("id", "1", Store.YES)); doc.add(new TextField("title", "谷歌地圖之父跳槽facebook ", Store.YES)); /* * 修改索引。參數: 詞條:根據這個詞條匹配到的全部文檔都會被修改 文檔信息:要修改的新的文檔數據 */ writer.updateDocument(new Term("id", "1"), doc); // 提交 writer.commit(); // 關閉 writer.close(); } /* * 演示:刪除索引 注意: 通常,爲了進行精確刪除,咱們會根據惟一字段來刪除。好比ID 若是是用Term刪除,要求ID也必須是字符串類型! */ @Test public void testDelete() throws Exception { // 建立目錄對象 Directory directory = FSDirectory.open(new File("E:\\luceneTest")); // 建立配置對象 IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer()); // 建立索引寫出工具 IndexWriter writer = new IndexWriter(directory, conf); // 根據詞條進行刪除 // writer.deleteDocuments(new Term("id", "1")); // 根據query對象刪除,若是ID是數值類型,那麼咱們能夠用數值範圍查詢鎖定一個具體的ID // Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, // true); // writer.deleteDocuments(query); // 刪除全部 writer.deleteAll(); // 提交 writer.commit(); // 關閉 writer.close(); }
// 高亮顯示 @Test public void testHighlighter() throws Exception { // 目錄對象 Directory directory = FSDirectory.open(new File("indexDir")); // 建立讀取工具 IndexReader reader = DirectoryReader.open(directory); // 建立搜索工具 IndexSearcher searcher = new IndexSearcher(reader); QueryParser parser = new QueryParser("title", new IKAnalyzer()); Query query = parser.parse("谷歌地圖"); // 格式化器 Formatter formatter = new SimpleHTMLFormatter("<em>", "</em>"); QueryScorer scorer = new QueryScorer(query); // 準備高亮工具 Highlighter highlighter = new Highlighter(formatter, scorer); // 搜索 TopDocs topDocs = searcher.search(query, 10); System.out.println("本次搜索共" + topDocs.totalHits + "條數據"); ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 獲取文檔編號 int docID = scoreDoc.doc; Document doc = reader.document(docID); System.out.println("id: " + doc.get("id")); String title = doc.get("title"); // 用高亮工具處理普通的查詢結果,參數:分詞器,要高亮的字段的名稱,高亮字段的原始值 String hTitle = highlighter.getBestFragment(new IKAnalyzer(), "title", title); System.out.println("title: " + hTitle); // 獲取文檔的得分 System.out.println("得分:" + scoreDoc.score); } }
// 排序 @Test public void testSortQuery() throws Exception { // 目錄對象 Directory directory = FSDirectory.open(new File("indexDir")); // 建立讀取工具 IndexReader reader = DirectoryReader.open(directory); // 建立搜索工具 IndexSearcher searcher = new IndexSearcher(reader); QueryParser parser = new QueryParser("title", new IKAnalyzer()); Query query = parser.parse("谷歌地圖"); // 建立排序對象,須要排序字段SortField,參數:字段的名稱、字段的類型、是否反轉若是是false,升序。true降序 Sort sort = new Sort(new SortField("id", SortField.Type.LONG, true)); // 搜索 TopDocs topDocs = searcher.search(query, 10,sort); System.out.println("本次搜索共" + topDocs.totalHits + "條數據"); ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 獲取文檔編號 int docID = scoreDoc.doc; Document doc = reader.document(docID); System.out.println("id: " + doc.get("id")); System.out.println("title: " + doc.get("title")); } }
// 分頁 @Test public void testPageQuery() throws Exception { // 實際上Lucene自己不支持分頁。所以咱們須要本身進行邏輯分頁。咱們要準備分頁參數: int pageSize = 2;// 每頁條數 int pageNum = 3;// 當前頁碼 int start = (pageNum - 1) * pageSize;// 當前頁的起始條數 int end = start + pageSize;// 當前頁的結束條數(不能包含) // 目錄對象 Directory directory = FSDirectory.open(new File("indexDir")); // 建立讀取工具 IndexReader reader = DirectoryReader.open(directory); // 建立搜索工具 IndexSearcher searcher = new IndexSearcher(reader); QueryParser parser = new QueryParser("title", new IKAnalyzer()); Query query = parser.parse("谷歌地圖"); // 建立排序對象,須要排序字段SortField,參數:字段的名稱、字段的類型、是否反轉若是是false,升序。true降序 Sort sort = new Sort(new SortField("id", Type.LONG, false)); // 搜索數據,查詢0~end條 TopDocs topDocs = searcher.search(query, end,sort); System.out.println("本次搜索共" + topDocs.totalHits + "條數據"); ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (int i = start; i < end; i++) { ScoreDoc scoreDoc = scoreDocs[i]; // 獲取文檔編號 int docID = scoreDoc.doc; Document doc = reader.document(docID); System.out.println("id: " + doc.get("id")); System.out.println("title: " + doc.get("title")); } }
- 當談論到查詢的相關性,很重要的一件事就是對於給定的查詢語句,如何計算文檔得分。文檔得分是一個用來描述查詢語句和文檔之間匹配程度的變量。若是你但願經過干預Lucene查詢來改變查詢結果的排序,你就須要對Lucene的得分計算有所理解。
計算文檔得分,考慮因素以下:
Lucene概念上的打分公式是這樣的:(TF/IDF公式的概念版)
上面的公式展現了布爾信息檢索模型和向量空間信息檢索模型的組合。咱們暫時不去討論它,直接見識下Lucene實際應用的打分公式:
能夠看到,文檔的分數其實是由查詢語句q和文檔d做爲變量的一個函數值。打分公式中有兩部分不直接依賴於查詢詞,它們是coord和queryNorm。公式的值是這樣計算的,coord和queryNorm兩大部分直接乘以查詢語句中每一個查詢詞計算值的總和。另外一方面,這個總和也是由每一個查詢詞的詞頻(tf),逆文檔頻率(idf),查詢詞的權重,還有norm,也就是前面說的length norm相乘而得的結果。聽上去有些複雜吧?不用擔憂,這些東西不須要所有記住。你只須要知道在進行文檔打分的時候,哪些因素是起決定做用的就能夠了。基本上,從前面的公式中能夠提煉出如下的幾個規則:
正如咱們所看到的那樣,Lucene會給具備這些特徵的文檔打最高分:文檔內容可以匹配到較多的稀有的搜索關鍵詞,文檔的域包含較少的Term,而且域中的Term可能是稀有的。簡而言之
將IKAnalyzer.cfg.xml和stopword.dic和xxx.dic文件複製到MyEclipse的src目錄下,再進行配置
IK Analyzer默認的停用詞詞典爲IKAnalyzer2012_u6/stopword.dic,這個停用詞詞典並不完整,只有30多個英文停用詞。能夠擴展停用詞字典,新增ext_stopword.dic,文件和IKAnalyzer.cfg.xml在同一目錄,編輯IKAnalyzer.cfg.xml把新增的停用詞字典寫入配置文件,多個停用詞字典用逗號隔開,以下所示。
<entry key="ext_stopwords">stopword.dic;ext_stopword.dic</entry>
接下來就能夠構建本身的搜索引擎了。
上面展現了lucene一些基本操做,更詳細的的工具類能夠訪問https://github.com/wangshiyu777/usefulDemo,分享了詳細Demo。