Lucene全文搜索原理與使用

本文中主要是對於Lucene全文搜索的基礎原理進行簡單的分析,以及Lucene實現全文搜索的流程,之後就是Lucene在Java中的最簡單使用:創建索引,查詢索引庫;
本文中使用的Lucene主要是4.10.3和6.0.0,兩個版本的原理相同,但是API的使用並不相同;
1、結構化數據與非結構化數據
2、非結構化數據搜索
3、全文搜索
4、搜索如何實現
5、Lucene實現全文搜索流程
6、Lucene的API使用
1、結構化數據與非結構化數據

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

2、非結構化數據搜索
根據上述1中所述的內容,所以兩者在搜索上也是同樣存在着一定的區別 (內容來自wiki):

  • 對於結構化的數據而言:對數據庫的搜索,用SQL語句。再如對元數據的搜索,如利用windows搜索對文件名,類型,修改時間進行搜索等。
  • 對於非結構化的數據而言:
    • 順序掃描法(Serial Scanning):所謂順序掃描,比如要找內容包含某一個字符串的文件,就是一個文檔一個文檔的看,對於每一個文檔,從頭看到尾,如果此文檔包含此字符串,則此文檔爲我們要找的文件,接着看下一個文件,直到掃描完所有的文件。如利用windows的搜索也可以搜索文件內容,只是相當的慢。
    • 全文檢索(Full-text Search):將非結構化數據中的一部分信息提取出來,重新組織,使其變得有一定結構,然後對此有一定結構的數據進行搜索,從而達到搜索相對較快的目的。這部分從非結構化數據中提取出的然後重新組織的信息,我們稱之索引。例如:字典。字典的拼音表和部首檢字表就相當於字典的索引,對每一個字的解釋是非結構化的,如果字典沒有音節表和部首檢字表,在茫茫辭海中找一個字只能順序掃描。然而字的某些信息可以提取出來進行結構化處理,比如讀音,就比較結構化,分聲母和韻母,分別只有幾種可以一一列舉,於是將讀音拿出來按一定的順序排列,每一項讀音都指向此字的詳細解釋的頁數。我們搜索時按結構化的拼音搜到讀音,然後按其指向的頁數,便可找到我們的非結構化數據——也即對字的解釋。這種先建立索引,再對索引進行搜索的過程就叫全文檢索(Full-text Search)。

3、全文搜索

  • 全文檢索是一種將文件中所有文本與檢索項匹配的文字資料檢索方法。全文檢索首先將要查詢的目標文檔中的詞提取出來,組成索引,通過查詢索引達到搜索目標文檔的目的。這種先建立索引,再對索引進行搜索的過程就叫全文檢索(Full-text Search)。
  • 全文檢索就是把文本中的內容拆分成若干個關鍵詞,然後根據關鍵詞創建索引。查詢時,根據關鍵詞查詢索引,最終找到包含關鍵詞的文章。整個過程類似於查字典的過程。(重點在於如何可以正確高效的拆分關鍵詞,然後根據關鍵詞創建索引
  • 全文檢索的應用領域
    • 搜索引擎:例如百度、谷歌、搜狗。
    • 站內搜索:例如論壇搜索,天涯論壇搜索、微博搜索。
    • 電商搜索:搜索的是商品信息。例如淘寶、京東。

4、搜索如何實現(以百度/谷歌的搜索業務作爲例子)
模擬如下:

5、Lucene實現全文搜索流程

  1. 創建文檔對象:爲每個文件對應的創建一個Document對象。把文件的屬性都保存到document對象中。需要爲每個屬性創建一個field(在lucene中叫做域),把field添加到文檔對象中。每個document都有一個唯一的編號。
  2. 分析文檔:針對document中的域進行分析,例如分析文件名、文件內容兩個域。先把文件內容域中的字符串根據空格進行分詞,把單詞進行統一轉換成小寫。把沒有意義的單詞叫做停用詞。把停用詞從詞彙列表中去掉。去掉標點符號。最終得到一個關鍵詞列表。每個關鍵詞叫做一個Term。Term中包含關鍵詞及其所在的域,不同的域中相當的單詞是不同的term。
  3. 創建索引:索引:爲了提高查詢速度的一個數據結構。在關鍵詞列表上創建一個索引;把索引和文檔對象寫入索引庫,並記錄關鍵詞和文檔對象的對應關係。

每個關鍵詞對應一鏈表,鏈表中的每個元素都是document對象的id。對所有文檔分析得出的語彙單元進行索引,索引的目的是爲了搜索,最終要實現只搜索被索引的語彙單元從而找到Document(文檔)。注意:創建索引是對語彙單元索引,通過詞語找文檔,這種索引的結構叫倒排索引結構。傳統方法是根據文件找到該文件的內容,在文件內容中匹配搜索關鍵字,這種方法是順序掃描方法,數據量大、搜索慢。倒排索引結構也叫反向索引結構,包括索引和文檔兩部分,索引即詞彙表,它的規模較小,而文檔集合較大。

6、Lucene的API使用(建議使用maven的工程)

1)創建索引(使用6.0.0的方式創建)

pom.xml

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

        <!-- http://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>6.0.0</version>
        </dependency>

        <!-- http://mvnrepository.com/artifact/org.apache.lucene/lucene-queryparser -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>6.0.0</version>
        </dependency>

        <!-- http://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-common -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>6.0.0</version>
        </dependency>

        <!-- http://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.5</version>
        </dependency>

    </dependencies>

IndexRepository.java

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;

import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
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.store.Directory;
import org.apache.lucene.store.FSDirectory;

/**
 * 索引存儲
 */
public class IndexRepository {
    // 注意:此處使用的是Lucene6.0.0最新版本與4.X版本有一些區別,可以查看源碼或者API進行了解
    public static void main(String[] args) throws IOException {
        // 指定索引庫的存放路徑,需要在系統中首先進行索引庫的創建
        // 指定索引庫存放路徑
        File indexrepository_file = new File("此處是索引存放地址");
        Path path = indexrepository_file.toPath();
        Directory directory = FSDirectory.open(path);
        // 讀取原始文檔內容
        File files = new File("此處是源文件地址");
        // 創建一個分析器對象
        // 使用標準分析器
        Analyzer analyzer = new StandardAnalyzer();
        // 創建一個IndexwriterConfig對象
        // 分析器
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        // 創建一個IndexWriter對象,對於索引庫進行寫操作
        IndexWriter indexWriter = new IndexWriter(directory, config);
        // 遍歷一個文件
        for (File f : files.listFiles()) {
            // 文件名
            String fileName = f.getName();
            // 文件內容
            @SuppressWarnings("deprecation")
            String fileContent = FileUtils.readFileToString(f);
            // 文件路徑
            String filePath = f.getPath();
            // 文件大小
            long fileSize = FileUtils.sizeOf(f);

            // 創建一個Document對象
            Document document = new Document();
            // 向Document對象中添加域信息
            // 參數:1、域的名稱;2、域的值;3、是否存儲;
            Field nameField = new TextField("name", fileName, Store.YES);
            Field contentField = new TextField("content", fileContent , Store.YES);
            // storedFiled默認存儲
            Field pathField = new StoredField("path", filePath);
            Field sizeField = new StoredField("size", fileSize);
            // 將域添加到document對象中
            document.add(nameField);
            document.add(contentField);
            document.add(pathField);
            document.add(sizeField);
            // 將信息寫入到索引庫中
            indexWriter.addDocument(document);

        }

        // 關閉indexWriter
        indexWriter.close();
    }

}

運行結果:

2)創建索引(使用4.10.3的方式創建)

pom.xml

    <dependencies>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <!-- http://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.5</version>
        </dependency>
        <!-- http://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-common -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>4.10.3</version>
        </dependency>
        <!-- http://mvnrepository.com/artifact/org.apache.lucene/lucene-queryparser -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>4.10.3</version>
        </dependency>
        <!-- http://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>4.10.3</version>
        </dependency>

    </dependencies>

IndexRepository.java

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

import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.document.Field.Store;
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.apache.lucene.util.Version;

/**
 * 索引的創建
 */
public class IndexRepository {

    public static void main(String[] args) throws IOException {
        Directory directory = FSDirectory.open(new File("此處是索引文件存放地址"));
        File files = new File("此處是源文件地址");

        Analyzer analyzer = new StandardAnalyzer();
        IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);

        IndexWriter indexWriter = new IndexWriter(directory,config);

        for (File f : files.listFiles()) {
            // 文件名
            String fileName = f.getName();
            // 文件內容
            @SuppressWarnings("deprecation")
            String fileContent = FileUtils.readFileToString(f);
            // 文件路徑
            String filePath = f.getPath();
            // 文件大小
            long fileSize = FileUtils.sizeOf(f);

            // 創建一個Document對象
            Document document = new Document();
            // 向Document對象中添加域信息
            // 參數:1、域的名稱;2、域的值;3、是否存儲;
            Field nameField = new TextField("name", fileName, Store.YES);
            Field contentField = new TextField("content", fileContent , Store.YES);
            // storedFiled默認存儲
            Field pathField = new StoredField("path", filePath);
            Field sizeField = new LongField("size", fileSize, Store.YES);
            // 將域添加到document對象中
            document.add(nameField);
            document.add(contentField);
            document.add(pathField);
            document.add(sizeField);
            // 將信息寫入到索引庫中
            indexWriter.addDocument(document);
        }

        indexWriter.close();
    }

}   

3)查詢索引庫
 

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.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;

/**
 * 文檔搜索
 * 通過關鍵詞搜索文檔
 *
 */
public class DocSearch {

    public static void main(String[] args) throws IOException {

        // 打開索引庫
        // 找到索引庫的位置
        Directory directory = FSDirectory.open(new File("此處是索引文件存放地址"));
        IndexReader indexReader = DirectoryReader.open(directory);
        // 創建一個IndexSearcher對象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // 創建一個查詢對象
        TermQuery query = new TermQuery(new Term("name","apache"));
        // 執行查詢
        // 返回的最大值,在分頁的時候使用
        TopDocs topDocs = indexSearcher.search(query, 5);
        // 取查詢結果總數量
        System.out.println("總共的查詢結果:" + topDocs.totalHits);
        // 查詢結果,就是documentID列表
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;

        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取對象document的對象id
            int docID = scoreDoc.doc;

            // 相關度得分
            float score = scoreDoc.score;

            // 根據ID去document對象
            Document document = indexSearcher.doc(docID);

            System.out.println("相關度得分:" + score);
            System.out.println("");
            System.out.println(document.get("name"));
            System.out.println("");
            // 另外的一種使用方法
            System.out.println(document.getField("content").stringValue());
            System.out.println(document.get("path"));
            System.out.println();
            System.out.println("=======================");
        }

        indexReader.close();
    }

}

運行結果:

發佈了25 篇原創文章 · 獲贊 17 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章