前言
本文的簡要內容:javascript
Lucene簡介css
體驗Lucene Demojava
Lucene 核心類介紹mysql
Lucene 索引文件格式程序員
Lucene簡介
Lucene是目前最流行的Java開源搜索引擎類庫,最新版本爲7.4.0。Lucene一般用於全文檢索,Lucene具備簡單高效跨平臺等特色,所以有很多搜索引擎都是基於Lucene構建的,例如:Elasticsearch,Solr等等。正則表達式
現代搜索引擎的兩大核心就是索引和搜索,創建索引的過程就是對源數據進行處理,例如過濾掉一些特殊字符或詞語,單詞大小寫轉換,分詞,創建倒排索引等支持後續高效準確的搜索。而搜索則是直接提供給用戶的功能,儘管面向的用戶不一樣,諸如百度,谷歌等互聯網公司以及各類企業都提供了各自的搜索引擎。搜索過程須要對搜索關鍵詞進行分詞等處理,而後再引擎內部構建查詢,還要根據相關度對搜索結果進行排序,最終把命中結果展現給用戶。sql
Lucene只是一個提供索引和查詢的類庫,並非一個應用,程序員須要根據本身的應用場景進行如數據獲取、數據預處理、用戶界面提供等工做。數據庫
搜索程序的典型組件以下所示:apache

下圖爲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

運行查詢類 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",0l, 100l, true, true);
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是一個徹底獨立的索引,能夠單獨用於搜索,索引涉及
爲新添加的documents建立新的segments
合併已經存在的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
參考:
Lucene初識及核心類介紹
Lucene核心類
Lucene 6.0 索引文件格式
Lucene實戰.pdf
本文分享自微信公衆號 - 小旋鋒(whirlysBigData)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。