Lucene 基礎知識

第一、什麼是全文檢索

1.1 數據分類(結構化和非結構化數據)

結構化數據:指具有固定格式或有限長度的數據,如數據庫,元數據等。
非結構化數據:指不定長或無固定格式的數據,如郵件,word文檔等磁盤上的文件

1.2 結構化數據搜索

常見的結構化數據也就是數據庫中的數據。數據檢索通常使用SQL

1.3 非結構化數據搜索

(1)順序掃描法:所謂順序掃描,比如要找內容包含某一個字符串的文件,就是一個文檔一個文檔的看,對於每一個文檔,從頭看到尾,如果此文檔包含此字符串,則此文檔爲我們要找的文件,接着看下一個文件,直到掃描完所有的文件。如利用windows的搜索也可以搜索文件內容,只是相當的慢。

(2)全文檢索:將非結構化數據中的一部分信息提取出來,重新組織,使其變得有一定結構,然後對此有一定結構的數據進行搜索,從而達到搜索相對較快的目的。這部分從非結構化數據中提取出的然後重新組織的信息,我們稱之索引。如字典的拼音表和部首檢字表就相當於字典的索引。

1.4 如何實現全文檢索

      Lucene是apache下的一個開放源代碼的全文檢索引擎工具包。提供了完整的查詢引擎和索引引擎,部分文本分析引擎。Lucene的目的是爲軟件開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能。

1.5 全文檢索應用場景

       對於數據量大、數據結構不固定的數據可採用全文檢索方式搜索,比如百度、Google等搜索引擎、論壇站內搜索、電商網站站內搜索等。

 

第二、Lucene 全文檢索流程和實現

2.1 Lucene 索引和搜索流程圖

1、綠色表示索引過程,對要搜索的原始內容進行索引構建一個索引庫,索引過程包括:
確定原始內容即要搜索的內容採集文檔創建文檔分析文檔索引文檔
    
2、紅色表示搜索過程,從索引庫中搜索內容,搜索過程包括:
用戶通過搜索界面創建查詢執行搜索,從索引庫搜索渲染搜索結果

2.2 Lucene 創建索引

實現步驟總結:

第一步:創建一個java工程,並導入jar包。
第二步:創建一個indexwriter對象。
1)指定索引庫的存放位置Directory對象
2)指定一個IndexWriterConfig對象。
第三步:創建document對象。
第四步:創建field對象,將field添加到document對象中。
第五步:使用indexwriter對象將document對象寫入索引庫,此過程進行索引創建。並將索引和document對象寫入索引庫。
第六步:關閉IndexWriter對象。

pom.xml 添加相關依賴:

        <!--lucene jar包 -->
		<dependency>
			<groupId>org.apache.lucene</groupId>
			<artifactId>lucene-core</artifactId>
			<version>7.4.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.lucene</groupId>
			<artifactId>lucene-analyzers-common</artifactId>
			<version>7.4.0</version>
		</dependency>
		<!-- apache common 通用工具類 -->
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.3.1</version>
		</dependency>

java 示例代碼:

package com.zzg.lucene;

import java.io.File;
import java.io.IOException;

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;

/**
 * 索引創建
 * @author Administrator
 *
 */
public class IndexCreate {

	public static void main(String[] args) throws Exception {
		 //指定索引庫存放的路徑
	    //D:\temp\index
	    Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
	    //索引庫還可以存放到內存中
	    //Directory directory = new RAMDirectory();
	    //創建indexwriterCofig對象
	    IndexWriterConfig config = new IndexWriterConfig();
	    //創建indexwriter對象
	    IndexWriter indexWriter = new IndexWriter(directory, config);
	    //原始文檔的路徑
	    File dir = new File("D:\\baiduNetdiskDownload\\searchsource");
	    for (File f : dir.listFiles()) {
	        //文件名
	        String fileName = f.getName();
	        //文件內容
	        String fileContent = FileUtils.readFileToString(f);
	        //文件路徑
	        String filePath = f.getPath();
	        //文件的大小
	        long fileSize  = FileUtils.sizeOf(f);
	        //創建文件名域
	        //第一個參數:域的名稱
	        //第二個參數:域的內容
	        //第三個參數:是否存儲
	        Field fileNameField = new TextField("filename", fileName, Field.Store.YES);
	        //文件內容域
	        Field fileContentField = new TextField("content", fileContent, Field.Store.YES);
	        //文件路徑域(不分析、不索引、只存儲)
	        Field filePathField = new TextField("path", filePath, Field.Store.YES);
	        //文件大小域
	        Field fileSizeField = new TextField("size", fileSize + "", Field.Store.YES);

	        //創建document對象
	        Document document = new Document();
	        document.add(fileNameField);
	        document.add(fileContentField);
	        document.add(filePathField);
	        document.add(fileSizeField);
	        //創建索引,並寫入索引庫
	        indexWriter.addDocument(document);
	    }
	    //關閉indexwriter
	    indexWriter.close();
	}

}

2.3 Lucene 查詢索引

實現步驟總結:

第一步:創建一個Directory對象,也就是索引庫存放的位置。
第二步:創建一個indexReader對象,需要指定Directory對象。
第三步:創建一個indexsearcher對象,需要指定IndexReader對象
第四步:創建一個TermQuery對象,指定查詢的域和查詢的關鍵詞。
第五步:執行查詢。
第六步:返回查詢結果。遍歷查詢結果並輸出。
第七步:關閉IndexReader對象

java 示例代碼:

package com.zzg.lucene;

import java.io.File;
import java.io.IOException;

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.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

/**
 * 查詢索引
 * @author Administrator
 *
 */
public class SearchIndex {

	public static void main(String[] args) throws Exception {
		//指定索引庫存放的路徑
	    //D:\temp\index
	    Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
	    //創建indexReader對象
	    IndexReader indexReader = DirectoryReader.open(directory);
	    //創建indexsearcher對象
	    IndexSearcher indexSearcher = new IndexSearcher(indexReader);
	    //創建查詢
	    Query query = new TermQuery(new Term("filename", "apache"));
	    //執行查詢
	    //第一個參數是查詢對象,第二個參數是查詢結果返回的最大值
	    TopDocs topDocs = indexSearcher.search(query, 10);
	    //查詢結果的總條數
	    System.out.println("查詢結果的總條數:"+ topDocs.totalHits);
	    //遍歷查詢結果
	    //topDocs.scoreDocs存儲了document對象的id
	    for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
	        //scoreDoc.doc屬性就是document對象的id
	        //根據document的id找到document對象
	        Document document = indexSearcher.doc(scoreDoc.doc);
	        System.out.println(document.get("filename"));
	        //System.out.println(document.get("content"));
	        System.out.println(document.get("path"));
	        System.out.println(document.get("size"));
	        System.out.println("-------------------------");
	    }
	    //關閉indexreader對象
	    indexReader.close();

	}

}

2.4 Lucene 標準分詞器(StandardAnalyzer 和SmartChineseAnalyzer)

(1)、StandardAnalyzer:
單字分詞:就是按照中文一個字一個字地進行分詞。如:“我愛中國”,
效果:“我”、“愛”、“中”、“國”。
(2)、SmartChineseAnalyzer
對中文支持較好,但擴展性差,擴展詞庫,禁用詞庫和同義詞庫等不好處理

 

pom.xml 文件添加lucene-analyzers-smartcn.jar 包

        <dependency>
			<groupId>org.apache.lucene</groupId>
			<artifactId>lucene-analyzers-smartcn</artifactId>
			<version>7.4.0</version>
		</dependency>

java 示例代碼:  

package com.zzg.lucene;

import java.io.IOException;

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;

/**
 * 標準分詞創建:StandardAnalyzer
 * 
 * @author Administrator
 *
 */
public class StandardAnalyzerCreate {

	public static void main(String[] args) throws IOException {
		String english = "Analysis is one of the main causes of slow indexing. Simply put, the more you analyze the slower analyze the indexing (in most cases).";
		String chinese = "張三說的確實在理。";

		Analyzer analyzer = new StandardAnalyzer();
		
		TokenStream tokenStream = analyzer.tokenStream("english", english);
		doToken(tokenStream);
		
		tokenStream = analyzer.tokenStream("chinese", chinese);
		doToken(tokenStream);
	}

	private static void doToken(TokenStream tokenStream) throws IOException {
		tokenStream.reset();
		CharTermAttribute cta = tokenStream.getAttribute(CharTermAttribute.class);
		while (tokenStream.incrementToken()) {
			System.out.print(cta.toString() + "|");
		}
		tokenStream.end();
		tokenStream.close();
	}

}
package com.zzg.lucene;

import java.io.IOException;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;

/**
 * 標準分詞創建:SmartChineseAnalyzer
 * @author Administrator
 *
 */
public class SmartChineseAnalyzerCreate {

	public static void main(String[] args) throws IOException {
		String english = "Analysis is one of the main causes of slow indexing. Simply put, the more you analyze the slower analyze the indexing (in most cases).";
		String chinese = "張三說的確實在理。";

		Analyzer analyzer = new SmartChineseAnalyzer();
		
		TokenStream tokenStream = analyzer.tokenStream("english", english);
		doToken(tokenStream);
		
		tokenStream = analyzer.tokenStream("chinese", chinese);
		doToken(tokenStream);
	}

	private static void doToken(TokenStream tokenStream) throws IOException {
		tokenStream.reset();
		CharTermAttribute cta = tokenStream.getAttribute(CharTermAttribute.class);
		while (tokenStream.incrementToken()) {
			System.out.print(cta.toString() + "|");
		}
		tokenStream.end();
		tokenStream.close();
	}
}

2.5 IKAnalyzer

使用方法:

第一步:把jar包添加到工程中

第二步:把配置文件和擴展詞典和停用詞詞典添加到classpath下

 

意:hotword.dic和ext_stopword.dic文件的格式爲UTF-8,注意是無BOM 的UTF-8 編碼。

也就是說禁止使用windows記事本編輯擴展詞典文件

java 示例代碼:

package com.zzg.lucene;

import java.io.IOException;
import java.io.StringReader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.wltea.analyzer.lucene.IKAnalyzer;

public class IKAnalyzerCreate {

	public static void main(String[] args) throws IOException {
		String chinese = "基於java語言開發的輕量級的中文分詞工具包";
		StringReader reader = new StringReader(chinese);
		Analyzer analyzer = new IKAnalyzer(true);
		
		
		TokenStream tokenStream = analyzer.tokenStream("chinese", reader);
		doToken(tokenStream);
	}

	private static void doToken(TokenStream tokenStream) throws IOException {
		tokenStream.reset();
		CharTermAttribute cta = tokenStream.getAttribute(CharTermAttribute.class);
		while (tokenStream.incrementToken()) {
			System.out.print(cta.toString() + "|");
		}
		tokenStream.end();
		tokenStream.close();
	}

}

注意: ikanalyzer 分詞對Lucene 版本的支持僅到Lucene 4.x版本,使用lucene 7.x 高版本集成ikanalyzer 分詞會提示如下錯誤信息:

Exception in thread "main" java.lang.AbstractMethodError: org.apache.lucene.analysis.Analyzer.createComponents(Ljava/lang/String;)Lorg/apache/lucene/analysis/Analyzer$TokenStreamComponents;
    at org.apache.lucene.analysis.Analyzer.tokenStream(Analyzer.java:162)
    at com.inspur.analysis.kg.util.IKAnalyzerTest.main(IKAnalyzerTest.java:22)

解決辦法:其他開發愛好者維護的ik-analyzer版本,支持Lucene5/6/7/8+版本, 且長期維護。

1、github 下載ik-analyzer 源碼,進行本地編譯安裝。

源碼下載地址:https://github.com/blueshen/ik-analyzer

2、Eclipse 導入ik-analyzer 源碼,更新pom.xml 文件依賴。

3、打開windows 的cmd 窗口指令,切換之ik-analyzer 所在目錄,執行如下指令:

mvn clean install -Dmaven.test.skip=true

java 示例代碼:集成IK 分詞的pom 文件依賴:

  <dependency>
             <groupId>org.wltea.ik-analyzer</groupId>
        	 <artifactId>ik-analyzer</artifactId>
             <version>8.4.0</version>
            <!--排除掉裏面舊的lucene包,因爲我們要重寫裏面的分析器和分詞器  -->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.lucene</groupId>
                    <artifactId>lucene-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.lucene</groupId>
                    <artifactId>lucene-queryparser</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.lucene</groupId>
                    <artifactId>lucene-analyzers-common</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

第三、Lucene 索引庫維護

3.1 索引庫的添加

3.1.1 Field域的屬性

是否分析:是否對域的內容進行分詞處理。前提是我們要對域的內容進行查詢。

是否索引:將Field分析後的詞或整個Field值進行索引,只有索引方可搜索到。
比如:商品名稱、商品簡介分析後進行索引,訂單號、身份證號不用分析但也要索引,這些將來都要作爲查詢條件。

是否存儲:將Field值存儲在文檔中,存儲在文檔中的Field纔可以從Document中獲取
比如:商品名稱、訂單號,凡是將來要從Document中獲取的Field都要存儲。

注意:是否存儲的標準:是否要將內容展示給用戶

3.1.2 文檔添加

    //索引庫存放路徑
    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();

3.2 索引庫刪除

3.2.1 索引庫全部刪除

         //索引庫存放路徑
        Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
        IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
        //創建一個indexwriter對象
        IndexWriter indexWriter = new IndexWriter(directory, config);
		//刪除全部索引
		indexWriter.deleteAll();
		//關閉indexwriter
		indexWriter.close();

 說明:將索引目錄的索引信息全部刪除,直接徹底刪除,無法恢復。

3.2.2 索引庫指定條件刪除

//索引庫存放路徑
Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
//創建一個indexwriter對象
IndexWriter indexWriter = new IndexWriter(directory, config);
//創建一個查詢條件
Query query = new TermQuery(new Term("filename", "apache"));
//根據查詢條件刪除
indexWriter.deleteDocuments(query);
//關閉indexwriter
indexWriter.close();

3.3 索引庫修改

//索引庫存放路徑
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", " Lucene 簡介 Lucene 是一個基於 Java 的全文信息檢索工具包," + "它不是一個完整的搜索應用程序,而是爲你的應用程序提供索引和搜索功能。",Field.Store.YES));
indexWriter.updateDocument(new Term("content", "java"), document);
//關閉indexWriter
indexWriter.close();

第四、Lucene 索引庫查詢

           對要搜索的信息創建Query查詢對象,Lucene會根據Query查詢對象生成最終的查詢語法,類似關係數據庫Sql語法一樣Lucene也有自己的查詢語法,比如:“name:lucene”表示查詢Field的name爲“lucene”的文檔信息。
    可通過兩種方法創建查詢對象:
    1)使用Lucene提供Query子類
    2)使用QueryParse解析查詢表達式

4.1 TermQuery

TermQuery,通過項查詢,TermQuery不使用分析器所以建議匹配不分詞的Field域查詢,比如訂單號、分類ID號等。
指定要查詢的域和要查詢的關鍵詞。

Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
    
//創建查詢對象
Query query = new TermQuery(new Term("content", "lucene"));
//執行查詢
TopDocs topDocs = indexSearcher.search(query, 10);
//共查詢到的document個數
System.out.println("查詢結果總數量:" + topDocs.totalHits);
//遍歷查詢結果
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
        Document document = indexSearcher.doc(scoreDoc.doc);
        System.out.println(document.get("filename"));
        //System.out.println(document.get("content"));
        System.out.println(document.get("path"));
        System.out.println(document.get("size"));
      }
 //關閉indexreader
 indexSearcher.getIndexReader().close();

4.2 數值範圍查詢

Query query = LongPoint.newRangeQuery("size", 0l, 10000l);

4.3 使用queryparser查詢

       通過QueryParser也可以創建Query,QueryParser提供一個Parse方法,此方法可以直接根據查詢語法來查詢。Query對象執行的查詢語法可通過System.out.println(query);查詢。需要使用到分析器。建議創建索引時使用的分析器和查詢索引時使用的分析器要一致。需要加入queryParser依賴的jar包。

       <dependency>
			<groupId>org.apache.lucene</groupId>
			<artifactId>lucene-queryparser</artifactId>
			<version>7.4.0</version>
		</dependency>
@Test
public void testQueryParser() throws Exception {
    Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
    IndexReader indexReader = DirectoryReader.open(directory);
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);
    //創建queryparser對象
    //第一個參數默認搜索的域
    //第二個參數就是分析器對象
    QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
    Query query = queryParser.parse("Lucene是java開發的");
    //執行查詢
    printResult(query, indexSearcher);
}

private void printResult(Query query, IndexSearcher indexSearcher) throws Exception {
    //執行查詢
    TopDocs topDocs = indexSearcher.search(query, 10);
    //共查詢到的document個數
    System.out.println("查詢結果總數量:" + topDocs.totalHits);
    //遍歷查詢結果
    for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
        Document document = indexSearcher.doc(scoreDoc.doc);
        System.out.println(document.get("filename"));
        //System.out.println(document.get("content"));
        System.out.println(document.get("path"));
        System.out.println(document.get("size"));
    }
    //關閉indexreader
    indexSearcher.getIndexReader().close();
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章