Lucene 7.4 初體驗

前言

本文的簡要內容:javascript

  1. Lucene簡介css

  2. 體驗Lucene Demojava

  3. Lucene 核心類介紹mysql

  4. Lucene 索引文件格式程序員

Lucene簡介

Lucene是目前最流行的Java開源搜索引擎類庫,最新版本爲7.4.0。Lucene一般用於全文檢索,Lucene具備簡單高效跨平臺等特色,所以有很多搜索引擎都是基於Lucene構建的,例如:Elasticsearch,Solr等等。正則表達式

現代搜索引擎的兩大核心就是索引和搜索,創建索引的過程就是對源數據進行處理,例如過濾掉一些特殊字符或詞語,單詞大小寫轉換,分詞,創建倒排索引等支持後續高效準確的搜索。而搜索則是直接提供給用戶的功能,儘管面向的用戶不一樣,諸如百度,谷歌等互聯網公司以及各類企業都提供了各自的搜索引擎。搜索過程須要對搜索關鍵詞進行分詞等處理,而後再引擎內部構建查詢,還要根據相關度對搜索結果進行排序,最終把命中結果展現給用戶。sql

Lucene只是一個提供索引和查詢的類庫,並非一個應用,程序員須要根據本身的應用場景進行如數據獲取、數據預處理、用戶界面提供等工做。數據庫

搜索程序的典型組件以下所示:apache

搜索程序的典型組件

下圖爲Lucene與應用程序的關係:微信

Lucene與應用程序的關係

體驗Lucene Demo

接下來先來看一個簡單的demo

note:
代碼在 start Lucene

引入 Maven 依賴

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <lucene.version>7.4.0</lucene.version>
    </properties>

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

        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>${lucene.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>${lucene.version}</version>
        </dependency>

    </dependencies>

索引類 IndexFiles.java

import org.apache.lucene.analysis.*;
import org.apache.lucene.analysis.standard.*;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.store.*;

import java.io.*;
import java.nio.charset.*;
import java.nio.file.*;
import java.nio.file.attribute.*;

public class IndexFiles {
    public static void main(String[] args) {
        String indexPath = "D:/lucene_test/index"// 創建索引文件的目錄
        String docsPath = "D:/lucene_test/docs"// 讀取文本文件的目錄

        Path docDir = Paths.get(docsPath);

        IndexWriter writer = null;
        try {
            // 存儲索引數據的目錄
            Directory dir = FSDirectory.open(Paths.get(indexPath));
            // 建立分析器
            Analyzer analyzer = new StandardAnalyzer();
            IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
            iwc.setOpenMode(IndexWriterConfig.OpenMode.CREATE);

            writer = new IndexWriter(dir, iwc);
            indexDocs(writer, docDir);

            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void indexDocs(final IndexWriter writer, Path path) throws IOException {
        if (Files.isDirectory(path)) {
            Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                    try {
                        indexDoc(writer, file);
                    } catch (IOException ignore) {
                        // 不索引那些不能讀取的文件,忽略該異常
                    }
                    return FileVisitResult.CONTINUE;
                }
            });
        } else {
            indexDoc(writer, path);
        }
    }

    private static void indexDoc(IndexWriter writer, Path file) throws IOException {
        try (InputStream stream = Files.newInputStream(file)) {
            // 建立一個新的空文檔
            Document doc = new Document();
            // 添加字段
            Field pathField = new StringField("path", file.toString(), Field.Store.YES);
            doc.add(pathField);
            Field contentsField = new TextField("contents",
                    new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)));
            doc.add(contentsField);
            System.out.println("adding " + file);
            // 寫文檔
            writer.addDocument(doc);
        }
    }
}

查詢類 SearchFiles.java

import org.apache.lucene.analysis.*;
import org.apache.lucene.analysis.standard.*;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.*;
import org.apache.lucene.search.*;
import org.apache.lucene.store.*;

import java.io.*;
import java.nio.charset.*;
import java.nio.file.*;

public class SearchFiles {
    public static void main(String[] args) throws Exception {
        String indexPath = "D:/lucene_test/index"// 創建索引文件的目錄
        String field = "contents";
        IndexReader reader = DirectoryReader.open(FSDirectory.open(Paths.get(indexPath)));
        IndexSearcher searcher = new IndexSearcher(reader);
        Analyzer analyzer = new StandardAnalyzer();

        BufferedReader in = null;
        in = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
        QueryParser parser = new QueryParser(field, analyzer);
        System.out.println("Enter query:");
        // 從Console讀取要查詢的語句
        String line = in.readLine();
        if (line == null || line.length() == -1) {
            return;
        }
        line = line.trim();
        if (line.length() == 0) {
            return;
        }

        Query query = parser.parse(line);
        System.out.println("Searching for:" + query.toString(field));
        doPagingSearch(searcher, query);
        in.close();
        reader.close();
    }

    private static void doPagingSearch(IndexSearcher searcher, Query query) throws IOException {
        // TopDocs保存搜索結果
        TopDocs results = searcher.search(query, 10);
        ScoreDoc[] hits = results.scoreDocs;
        int numTotalHits = Math.toIntExact(results.totalHits);
        System.out.println(numTotalHits + " total matching documents");
        for (ScoreDoc hit : hits) {
            Document document = searcher.doc(hit.doc);
            System.out.println("文檔:" + document.get("path"));
            System.out.println("相關度:" + hit.score);
            System.out.println("================================");
        }

    }
}

測試

首先建立文件夾 D:\lucene_test,在 lucene_test 下再建立 docs 文件夾,用來存儲要索引的測試文件

docs 下建立3個文件 test1.txt, test2.txt, test3.txt,分別寫入 hello world、 hello lucene、 hello elasticsearch

運行索引類 IndexFiles.java,可看到Console輸出

adding D:\lucene_test\docs\test1.txt
adding D:\lucene_test\docs\test2.txt
adding D:\lucene_test\docs\test3.txt
Lucene的索引文件

運行查詢類 SearchFiles.java,搜索 hello ,三個文件相關度同樣

Enter query:
hello
Searching for:hello
3 total matching documents
文檔:D:\lucene_test\docs\test1.txt
相關度:0.13353139
================================
文檔:D:\lucene_test\docs\test2.txt
相關度:0.13353139
================================
文檔:D:\lucene_test\docs\test3.txt
相關度:0.13353139
================================

搜索 hello lucene,test2.txt的相關度比其餘兩個高

Enter query:
hello lucene
Searching for:hello lucene
3 total matching documents
文檔:D:\lucene_test\docs\test2.txt
相關度:1.1143606
================================
文檔:D:\lucene_test\docs\test1.txt
相關度:0.13353139
================================
文檔:D:\lucene_test\docs\test3.txt
相關度:0.13353139
================================

Lucene 核心類介紹

核心索引類

IndexWriter

進行索引寫操做的一箇中心組件
不能進行讀取和搜索

Directory

Directory表明Lucene索引的存放位置
經常使用的實現:
    FSDerectory:表示一個存儲在文件系統中的索引的位置
    RAMDirectory:表示一個存儲在內存當中的索引的位置
做用:
    IndexWriter經過獲取Directory的一個具體實現,在Directory指向的位置中操做索引

Analyzer

Analyzer,分析器,至關於篩子,對內容進行過濾,分詞,轉換等
做用:把過濾以後的數據交給indexWriter進行索引

Document

用來存放文檔(數據),該文檔爲非結構化數據中抓取的相關數據
經過Field(域)組成Document,相似於mysql中的一個個字段組成的一條記錄

Field

Document中的一個字段

核心搜索類

IndexSearcher

IndexSearcher在創建好的索引上進行搜索
它只能以 只讀 的方式打開一個索引,因此能夠有多個IndexSearcher的實例在一個索引上進行操做

Term

Term是搜索的基本單元,一個Term由 key:value 組成(相似於mysql中的  字段名稱=查詢的內容)
例子: Query query = new TermQuery(new Term("filename""lucene"));

Query

Query是一個抽象類,用來將用戶輸入的查詢字符串封裝成Lucene可以識別的Query

TermQuery

Query子類,Lucene支持的最基本的一個查詢類
例子:TermQuery termQuery = new TermQuery(new Term("filename""lucene"));

BooleanQuery

BooleanQUery,布爾查詢,是一個組合Query(多個查詢條件的組合)
BooleanQuery是能夠嵌套的

栗子:
BooleanQuery query = new BooleanQuery();
BooleanQuery query2 = new BooleanQuery();
TermQuery termQuery1 = new TermQuery(new Term("fileName""lucene"));
TermQuery termQuery2 = new TermQuery(new Term("fileName""name"));
query2.add(termQuery1, Occur.SHOULD);
query.add(termQuery2, Occur.SHOULD);
query.add(query2, Occur.SHOULD);;        //BooleanQuery是能夠嵌套的

Occur枚舉:
    MUST
    SHOULD
    FILTER
    MUST_NOT

NumericRangeQuery

數字區間查詢
栗子:
Query newLongRange = NumericRangeQuery.newLongRange("fileSize",0l100ltruetrue);

PrefixQuery

前綴查詢,查詢分詞中含有指定字符開頭的內容
栗子:
PrefixQuery query = new PrefixQuery(new Term("fileName","hell"));

PhraseQuery

短語查詢
栗子1
    PhraseQuery query = new PhraseQuery();
    query.add(new Term("fileName","lucene"));

FuzzyQuery

模糊查詢
栗子:
FuzzyQuery query = new FuzzyQuery(new Term("fileName","lucene"));

WildcardQuery

通配符查詢:
* :任意字符(0或多個)
? : 一個字符

栗子:
WildcardQuery query = new WildcardQuery(new Term("fileName","*"));

RegexQuery

正則表達式查詢
栗子:搜索含有最少1個字符,最多6個字符的
RegexQuery query = new RegexQuery(new Term("fileName","[a-z]{1,6}"));

MultiFieldQueryParser

查詢多個field
栗子:
String[] fields = {"fileName","fileContent"};
MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, new StandardAnalyzer());
Query query = queryParser.parse("fileName:lucene AND filePath:a");

TopDocs

TopDocs類是一個簡單的指針容器,指針通常指向前N個排名的搜索結果,搜索結果即匹配條件的文檔
TopDocs會記錄前N個結果中每一個結果的int docID和浮點數型分數(反映相關度)

栗子:
    TermQuery searchingBooks = new TermQuery(new Term("subject","search")); 
    Directory dir = TestUtil.getBookIndexDirectory();
    IndexSearcher searcher = new IndexSearcher(dir);
    TopDocs matches = searcher.search(searchingBooks, 10);

Lucene 6.0 索引文件格式

倒排索引

談到倒排索引,那麼首先看看正排是什麼樣子的呢?假設文檔1包含【中文、英文、日文】,文檔2包含【英文、日文、韓文】,文檔3包含【韓文,中文】,那麼根據文檔去查找內容的話

文檔1->【中文、英文、日文】
文檔2->【英文、日文、韓文】
文檔3->【韓文,中文】

反過來,根據內容去查找文檔

中文->【文檔一、文檔3】
英文->【文檔一、文檔2】
日文->【文檔一、文檔2】
韓文->【文檔二、文檔3】

這就是倒排索引,而Lucene擅長的也正在於此

段(Segments)

Lucene的索引多是由多個子索引或Segments組成。每一個Segment是一個徹底獨立的索引,能夠單獨用於搜索,索引涉及

  1. 爲新添加的documents建立新的segments

  2. 合併已經存在的segments

搜索可能涉及多個segments或多個索引,每一個索引可能由一組segments組成

文檔編號

Lucene經過一個整型的文檔編號指向每一個文檔,第一個被加入索引的文檔編號爲0,後續加入的文檔編號依次遞增。
注意文檔編號是可能發生變化的,因此在Lucene外部存儲這些值時須要格外當心。

索引結構概述

每一個segment索引包括信息

  • Segment info:包含有關segment的元數據,例如文檔編號,使用的文件

  • Field names:包含索引中使用的字段名稱集合

  • Stored Field values:對於每一個document,它包含屬性-值對的列表,其中屬性是字段名稱。這些用於存儲有關文檔的輔助信息,例如其標題、url或訪問數據庫的標識符

  • Term dictionary:包含全部文檔的全部索引字段中使用的全部terms的字典。字典還包括包含term的文檔編號,以及指向term的頻率和接近度的指針

  • Term Frequency data:對於字典中的每一個term,包含該term的全部文檔的數量以及該term在該文檔中的頻率,除非省略頻率(IndexOptions.DOCS)

  • Term Proximity data:對於字典中的每一個term,term在每一個文檔中出現的位置。注意,若是全部文檔中的全部字段都省略位置數據,則不會存在

  • Normalization factors:對於每一個文檔中的每一個字段,存儲一個值,該值將乘以該字段上的匹配的分數

  • Term Vectors:對於每一個文檔中的每一個字段,能夠存儲term vector,term vector由term文本和term頻率組成

  • Per-document values:與存儲的值相似,這些也以文檔編號做爲key,但一般旨在被加載到主存儲器中以用於快速訪問。存儲的值一般用於彙總來自搜索的結果,而每一個文檔值對於諸如評分因子是有用的

  • Live documents:一個可選文件,指示哪些文檔是活動的

  • Point values:可選的文件對,記錄索引字段尺寸,以實現快速數字範圍過濾和大數值(例如BigInteger、BigDecimal(1D)、地理形狀交集(2D,3D))

文件命名

屬於一個段的全部文件具備相同的名稱和不一樣的擴展名。當使用複合索引文件,這些文件(除了段信息文件、鎖文件和已刪除的文檔文件)將壓縮成單個.cfs文件。當任何索引文件被保存到目錄時,它被賦予一個從未被使用過的文件名字

複合索引文件

文件擴展名摘要

名稱 文件擴展名 簡短描述
Segments File segments_N 保存了一個提交點(a commit point)的信息
Lock File write.lock 防止多個IndexWriter同時寫到一份索引文件中
Segment Info .si 保存了索引段的元數據信息
Compound File .cfs,.cfe 一個可選的虛擬文件,把全部索引信息都存儲到複合索引文件中
Fields .fnm 保存fields的相關信息
Field Index .fdx 保存指向field data的指針
Field Data .fdt 文檔存儲的字段的值
Term Dictionary .tim term詞典,存儲term信息
Term Index .tip 到Term Dictionary的索引
Frequencies .doc 由包含每一個term以及頻率的docs列表組成
Positions .pos 存儲出如今索引中的term的位置信息
Payloads .pay 存儲額外的per-position元數據信息,例如字符偏移和用戶payloads
Norms .nvd,.nvm .nvm文件保存索引字段加權因子的元數據,.nvd文件保存索引字段加權數據
Per-Document Values .dvd,.dvm .dvm文件保存索引文檔評分因子的元數據,.dvd文件保存索引文檔評分數據
Term Vector Index .tvx 將偏移存儲到文檔數據文件中
Term Vector Documents .tvd 包含有term vectors的每一個文檔信息
Term Vector Fields .tvf 字段級別有關term vectors的信息
Live Documents .liv 哪些是有效文件的信息
Point values .dii,.dim 保留索引點,若是有的話

鎖文件

默認狀況下,存儲在索引目錄中的鎖文件名爲 write.lock。若是鎖目錄與索引目錄不一樣,則鎖文件將命名爲「XXXX-write.lock」,其中XXXX是從索引目錄的完整路徑導出的惟一前綴。此鎖文件確保每次只有一個寫入程序在修改索引。

更多內容請訪問個人我的博客:http://laijianfeng.org
參考:

  1. Lucene初識及核心類介紹

  2. Lucene核心類

  3. Lucene 6.0 索引文件格式

  4. Lucene實戰.pdf


本文分享自微信公衆號 - 小旋鋒(whirlysBigData)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索