Lucene系列(一)快速入門

系列文章:html

Lucene系列(一)快速入門java

Lucene系列(二)luke使用及索引文檔的基本操做git

Lucene系列(三)查詢及高亮github

<font color="#0066CC">Lucene是什麼?</font>

<font color="#0066CC">Lucene在維基百科的定義</font>面試

Lucene是一套用於 全文檢索和搜索的開放源代碼程序庫,由Apache軟件基金會支持和提供。Lucene提供了一個簡單卻強大的應用程序接口,可以作全文索引和搜索,在Java開發環境裏Lucene是一個成熟的免費開放源代碼工具;就其自己而論, Lucene是如今而且是這幾年,最受歡迎的免費Java信息檢索程序庫

另外,Lucene不提供爬蟲功能,若是須要獲取內容須要本身創建爬蟲應用。
Lucene只作索引和搜索工做。數據庫

<font color="#0066CC">Lucene官網</font>apache

http://lucene.apache.org/數組

打開Luncene官網你會發現Lucene版本更新的太快了,如今最新的版本已是7.2.1。不過這也變相說明了Luncene這個開源庫的火爆。微信

<font color="#0066CC">Lucenesolr</font>工具

我想提到Lucene,不得不提solr了。

不少剛接觸Lucene和Solr的人都會問這個明顯的問題:我應該使用Lucene仍是Solr?

答案很簡單:若是你問本身這個問題,在99%的狀況下,你想使用的是Solr. 形象的來講Solr和Lucene之間關係的方式是汽車及其引擎。 你不能駕駛一臺發動機,但能夠開一輛汽車。 一樣,Lucene是一個程序化庫,您不能按原樣使用,而Solr是一個完整的應用程序,您能夠當即使用它。(參考:Lucene vs Solr

<font color="#0066CC">全文檢索是什麼?</font>

<font color="#0066CC">全文檢索在百度百科的定義</font>

全文數據庫是全文檢索系統的主要構成部分。所謂全文數據庫是將一個完整的信息源的所有內容轉化爲計算機能夠識別、處理的信息單元而造成的數據集合。全文數據庫不只存儲了信息,並且還有對全文數據進行詞、字、段落等更深層次的編輯、加工的功能,並且全部全文數據庫無一不是海量信息數據庫。

全文檢索首先將要查詢的目標文檔中的詞提取出來,組成索引,經過查詢索引達到搜索目標文檔的目的。這種先創建索引,再對索引進行搜索的過程就叫全文檢索(Full-text Search)。

全文檢索(Full-Text Retrieval)是指以文本做爲檢索對象,找出含有指定詞彙的文本。

全面、準確和快速是衡量全文檢索系統的關鍵指標。

關於全文檢索,咱們要知道:

  • 只處理文本。
  • 不處理語義。
  • 搜索時英文不區分大小寫。
  • 結果列表有相關度排序。(查出的結果若是沒有相關度排序,那麼系統不知道我想要的結果在哪一頁。咱們在使用百度搜索時,通常不須要翻頁,爲何?由於百度作了相關度排序:爲每一條結果打一個分數,這條結果越符合搜索條件,得分就越高,叫作相關度得分,結果列表會按照這個分數由高到低排列,因此第1頁的結果就是咱們最想要的結果。)

在信息檢索工具中,全文檢索是最具通用性和實用性的。

參考:https://zhuanlan.zhihu.com/p/...

<font color="#0066CC">全文檢索和數據庫搜索的區別</font>

簡單來講,這二者解決的問題是不同。數據庫搜索在匹配效果、速度、效率等方面都遜色於全文檢索。下面咱們的一個例子就能很清楚說明這一點。

<font color="#0066CC">Lucene實現全文檢索流程是什麼?</font>

Lucene實現全文檢索流程
全文檢索的流程分爲兩大部分:索引流程搜索流程

  • 索引流程:即採集數據構建文檔對象分析文檔(分詞)建立索引。
  • 搜索流程:即用戶經過搜索界面建立查詢執行搜索,搜索器從索引庫搜索渲染搜索結果

咱們在下面的一個程序中,對這個全文檢索的流程會有進一步的瞭解。

<font color="#0066CC">Lucene實現向文檔寫索引並讀取文檔</font>

截止2018/3/30,用到的jar包結爲最新。

程序用到的數據下載地址:

連接:https://pan.baidu.com/s/1ccgrCCRBBGOL-fmmOLrxlQ

密碼:vyof

  1. <font color="#00CC00">建立Maven項目,並添加相關jar包依賴</font>
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->
        <!-- Lucene核心庫 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>7.2.1</version>
        </dependency>
        <!-- Lucene解析庫 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>7.2.1</version>
        </dependency>
        <!-- Lucene附加的分析庫 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>7.2.1</version>
        </dependency>
  1. <font color="#00CC00">向文檔裏寫索引</font>
package lucene_demo1;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Paths;

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.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;
/**
 * 
 *TODO  索引文件
 * @author Snaiclimb
 * @date 2018年3月30日
 * @version 1.8
 */
public class Indexer {
    // 寫索引實例
    private IndexWriter writer;

    /**
     * 構造方法 實例化IndexWriter
     * 
     * @param indexDir
     * @throws IOException
     */
    public Indexer(String indexDir) throws IOException {
        //獲得索引所在目錄的路徑  
        Directory directory = FSDirectory.open(Paths.get(indexDir));
        // 標準分詞器
        Analyzer analyzer = new StandardAnalyzer();
        //保存用於建立IndexWriter的全部配置。
        IndexWriterConfig iwConfig = new IndexWriterConfig(analyzer);
        //實例化IndexWriter  
        writer = new IndexWriter(directory, iwConfig);
    }

    /**
     * 關閉寫索引
     * 
     * @throws Exception
     * @return 索引了多少個文件
     */
    public void close() throws IOException {
        writer.close();
    }

    public int index(String dataDir) throws Exception {
        File[] files = new File(dataDir).listFiles();
        for (File file : files) {
            //索引指定文件
            indexFile(file);
        }
        //返回索引了多少個文件
        return writer.numDocs();

    }

    /**
     * 索引指定文件
     * 
     * @param f
     */
    private void indexFile(File f) throws Exception {
        //輸出索引文件的路徑
        System.out.println("索引文件:" + f.getCanonicalPath());
        //獲取文檔,文檔裏再設置每一個字段
        Document doc = getDocument(f);
        //開始寫入,就是把文檔寫進了索引文件裏去了;
        writer.addDocument(doc);
    }

    /**
     * 獲取文檔,文檔裏再設置每一個字段
     * 
     * @param f
     * @return document 
     */
    private Document getDocument(File f) throws Exception {
        Document doc = new Document();
        //把設置好的索引加到Document裏,以便在肯定被索引文檔
        doc.add(new TextField("contents", new FileReader(f)));
        //Field.Store.YES:把文件名存索引文件裏,爲NO就說明不須要加到索引文件裏去 
        doc.add(new TextField("fileName", f.getName(), Field.Store.YES));
        //把完整路徑存在索引文件裏  
        doc.add(new TextField("fullPath", f.getCanonicalPath(), Field.Store.YES));
        return doc;
    }

    public static void main(String[] args) {
        //索引指定的文檔路徑
        String indexDir = "D:\\lucene\\dataindex";
        ////被索引數據的路徑  
        String dataDir = "D:\\lucene\\data";
        Indexer indexer = null;
        int numIndexed = 0;
        //索引開始時間  
        long start = System.currentTimeMillis();
        try {
            indexer = new Indexer(indexDir);
            numIndexed = indexer.index(dataDir);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                indexer.close();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        //索引結束時間
        long end = System.currentTimeMillis();
        System.out.println("索引:" + numIndexed + " 個文件 花費了" + (end - start) + " 毫秒");
    }

}

運行效果:
運行效果圖
咱們查看D:\lucene\dataindex文件夾。咱們發現多了一些東西,這些東西就是咱們立刻用來全文搜索的索引。

//索引指定的文檔路徑
String indexDir = "D:\\lucene\\dataindex";

D:\lucene\dataindex文件夾

Mark博文:Lucene的索引文件格式(1)

  1. <font color="#00CC00">全文檢索測試</font>
package lucene_demo1;

import java.nio.file.Paths;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
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.Directory;
import org.apache.lucene.store.FSDirectory;
/**
 * 根據索引搜索
 *TODO
 * @author Snaiclimb
 * @date 2018年3月25日
 * @version 1.8
 */
public class Searcher {

    public static void search(String indexDir, String q) throws Exception {

        // 獲得讀取索引文件的路徑
        Directory dir = FSDirectory.open(Paths.get(indexDir));
        // 經過dir獲得的路徑下的全部的文件
        IndexReader reader = DirectoryReader.open(dir);
        // 創建索引查詢器
        IndexSearcher is = new IndexSearcher(reader);
        // 實例化分析器
        Analyzer analyzer = new StandardAnalyzer();
        // 創建查詢解析器
        /**
         * 第一個參數是要查詢的字段; 第二個參數是分析器Analyzer
         */
        QueryParser parser = new QueryParser("contents", analyzer);
        // 根據傳進來的p查找
        Query query = parser.parse(q);
        // 計算索引開始時間
        long start = System.currentTimeMillis();
        // 開始查詢
        /**
         * 第一個參數是經過傳過來的參數來查找獲得的query; 第二個參數是要出查詢的行數
         */
        TopDocs hits = is.search(query, 10);
        // 計算索引結束時間
        long end = System.currentTimeMillis();
        System.out.println("匹配 " + q + " ,總共花費" + (end - start) + "毫秒" + "查詢到" + hits.totalHits + "個記錄");
        // 遍歷hits.scoreDocs,獲得scoreDoc
        /**
         * ScoreDoc:得分文檔,即獲得文檔 scoreDocs:表明的是topDocs這個文檔數組
         * 
         * @throws Exception
         */
        for (ScoreDoc scoreDoc : hits.scoreDocs) {
            Document doc = is.doc(scoreDoc.doc);
            System.out.println(doc.get("fullPath"));
        }

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

    public static void main(String[] args) {
        String indexDir = "D:\\lucene\\dataindex";
        //咱們要搜索的內容
        String q = "Jean-Philippe sdsds Barrette-LaPierre";
        try {
            search(indexDir, q);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

上面咱們搜索的是:"Jean-Philippe Barrette-LaPierre";即便你:"Jean-Philippe ssss Barrette-LaPierre"這樣搜索也仍是搜索到,覺得Lucene對其進行了分詞,對中文無效。
 全文搜索

<font color="#0066CC">Lucene實現全文檢索流程是什麼?</font>

咱們剛剛實現的程序已經清楚地向咱們展現了Lucene實現全文檢索流程,咱們再來回顧一下。
Lucene實現全文檢索流程

  • 在Lucene中,採集數據(從網站爬取或鏈接數據庫)就是爲了建立索引,建立索引須要先將採集的原始數據加工爲文檔,再由文檔分詞產生索引。文檔(Document) 中包含若干個Field域。
  • IndexWriter是索引過程的核心組件,經過IndexWriter能夠建立新索引、更新索引、刪除索引操做。IndexWriter須要經過Directory對索引進行存儲操做。
  • Directory描述了索引的存儲位置,底層封裝了I/O操做,負責對索引進行存儲。它是一個抽象類,它的子類經常使用的包括FSDirectory(在文件系統存儲索引)、RAMDirectory(在內存存儲索引)。
  • 在對Docuemnt中的內容索引以前須要使用分詞器進行分詞 ,分詞的主要過程就是分詞、過濾兩步。 分詞就是將採集到的文檔內容切分紅一個一個的詞,具體應該說是將Document中Field的value值切分紅一個一個的詞。

過濾包括去除標點符號、去除停用詞(的、是、a、an、the等)、大寫轉小寫、詞的形還原(複數形式轉成單數形參、過去式轉成如今式等)。

  • 停用詞是爲節省存儲空間和提升搜索效率,搜索引擎在索引頁面或處理搜索請求時會自動忽略某些字或詞,這些字或詞即被稱爲Stop Words(停用詞)。好比語氣助詞、副詞、介詞、鏈接詞等,一般自身並沒有明確的意義,只有將其放入一個完整的句子中才有必定做用,如常見的「的」、「在」、「是」、「啊」等。

Lucene中自帶了StandardAnalyzer,它能夠對英文進行分詞。

參照:https://zhuanlan.zhihu.com/p/...

<font color="#0066CC">Lucene工做原理總結</font>

<font color="#00CC00">一、索引流程</font>

從原始文件中提取一些能夠用來搜索的數據(封裝成各類Field),把各field再封裝成document,而後對document進行分析(對各字段分詞),獲得一些索引目錄寫入索引庫,document自己也會被寫入一個文檔信息庫;

<font color="#00CC00">二、搜索流程</font>

根據關鍵詞解析(queryParser)出查詢條件query(Termquery),利用搜索工具(indexSearcher)去索引庫獲取文檔id,而後再根據文檔id去文檔信息庫獲取文檔信息

分詞器不一樣,創建的索引數據就不一樣;比較通用的一箇中文分詞器IKAnalyzer的用法

<font color="#00CC00">三、相關度得分</font>

a) 在創建索引的時候,能夠給指定文檔的指定域設置一個權重

Field.setBoosts()(如今5.5版本以前的是這樣設置權重,後面的不是了)

b) 在搜索的時候,能夠給不一樣的搜索域設置不一樣的權重

Boosts = new HashMap<String,Float>

MultiFieldsQueryParser(fields,analyzer,boosts)

歡迎關注個人微信公衆號:「Java面試通關手冊」(堅持原創,分享美文,分享各類Java學習資源,面試題,以及企業級Java實戰項目回覆關鍵字免費領取):
微信公衆號

Lucene我想暫時先更新到這裏,僅僅這三篇文章想掌握Lucene是遠遠不夠的。另外我這裏三篇文章都用的最新的jar包,Lucene更新太快,5系列後的版本和以前的有些地方仍是有挺大差距的,就好比爲文檔域設置權值的setBoost方法6.6之後已經被廢除了等等。由於時間有限,因此我就草草的看了一下Lucene的官方文檔,大多數內容仍是看java1234網站的這個視頻來學習的,而後在版本和部分代碼上作了改進。截止2018/4/1,上述代碼所用的jar包皆爲最新。

最後推薦一下本身以爲還不錯的Lucene學習網站/博客:

官方網站:Welcome to Apache Lucene

Github:Apache Lucene and Solr

Lucene專欄

搜索系統18:lucene索引文件結構

Lucene6.6的介紹和使用

相關文章
相關標籤/搜索