Lucene介紹
lucene官方使用手冊: https://lucene.apache.org/core/8_5_0/index.html
1. Lucene簡介
最受歡迎的java開源全文搜索引擎開發工具包。提供了完整的查詢引擎和索引引擎,部分文本分詞引擎(英文與德文兩種西方語言)。Lucene的目的是爲軟件開發人員提供一個簡單易用的工具包,以方便在目標系統中實現全文檢索功能,或者是以此爲基礎建立起完整的全文檢索引擎。是Apache的子項目,網址:http://lucene.apache.org/
2. Lucene用途
爲軟件開發人員提供一個簡單易用的工具包,以方便在目標系統中實現全文檢索功能,或者是以此爲基礎建立起完整的全文檢索引擎。
3. Lucene適用場景
在應用中爲數據庫中的數據提供全文檢索實現。
開發獨立的搜索引擎服務、系統
4. Lucene的特性
1、穩定、索引性能高
每小時能夠索引150GB以上的數據。
對內存的要求小——只需要1MB的堆內存
增量索引和批量索引一樣快。
索引的大小約爲索引文本大小的20%~30%。
2、高效、準確、高性能的搜索算法
良好的搜索排序。
強大的查詢方式支持:短語查詢、通配符查詢、臨近查詢、範圍查詢等。
支持字段搜索(如標題、作者、內容)。
可根據任意字段排序
支持多個索引查詢結果合併
支持更新操作和查詢操作同時進行
支持高亮、join、分組結果功能
速度快
可擴展排序模塊,內置包含向量空間模型、BM25模型可選
可配置存儲引擎
3、跨平臺
純java編寫。
作爲Apache開源許可下的開源項目,你可在商業或開源項目中使用。
Lucene有多種語言實現版可選(如C、C++、Python等),不光是JAVA。
Lucene使用
1. 先引入依賴lucene依賴
<properties>
<lunece.version>4.10.2</lunece.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- lucene核心庫 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>${lunece.version}</version>
</dependency>
<!-- Lucene的查詢解析器 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>${lunece.version}</version>
</dependency>
<!-- lucene的默認分詞器庫 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>${lunece.version}</version>
</dependency>
<!-- lucene的高亮顯示 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>${lunece.version}</version>
</dependency>
</dependencies>
它的核心包結構
創建索引
// 創建索引
@Test
public void testCreate() throws Exception{
//1 創建文檔對象
Document document = new Document();
// 創建並添加字段信息。參數:字段的名稱、字段的值、是否存儲,這裏選Store.YES代表存儲到文檔列表。Store.NO代表不存儲
document.add(new StringField("id", "1", Field.Store.YES));
// 這裏我們title字段需要用TextField,即創建索引又會被分詞。StringField會創建索引,但是不會被分詞
document.add(new TextField("title", "谷歌地圖之父跳槽facebook", Field.Store.YES));
//2 索引目錄類,指定索引在硬盤中的位置
Directory directory = FSDirectory.open(new File("d:\\indexDir"));
//3 創建分詞器對象
Analyzer analyzer = new StandardAnalyzer();
//4 索引寫出工具的配置對象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
//5 創建索引的寫出工具類。參數:索引的目錄和配置信息
IndexWriter indexWriter = new IndexWriter(directory, conf);
//6 把文檔交給IndexWriter
indexWriter.addDocument(document);
//7 提交
indexWriter.commit();
//8 關閉
indexWriter.close();
}
批量創建索引
// 批量創建索引
@Test
public void testCreate2() throws Exception{
// 創建文檔的集合
Collection<Document> docs = new ArrayList<>();
// 創建文檔對象
Document document1 = new Document();
document1.add(new StringField("id", "1", Field.Store.YES));
document1.add(new TextField("title", "谷歌地圖之父跳槽facebook", Field.Store.YES));
docs.add(document1);
// 創建文檔對象
Document document2 = new Document();
document2.add(new StringField("id", "2", Field.Store.YES));
document2.add(new TextField("title", "谷歌地圖之父加盟FaceBook", Field.Store.YES));
docs.add(document2);
// 創建文檔對象
Document document3 = new Document();
document3.add(new StringField("id", "3", Field.Store.YES));
document3.add(new TextField("title", "谷歌地圖創始人拉斯離開谷歌加盟Facebook", Field.Store.YES));
docs.add(document3);
// 創建文檔對象
Document document4 = new Document();
document4.add(new StringField("id", "4", Field.Store.YES));
document4.add(new TextField("title", "谷歌地圖之父跳槽Facebook與Wave項目取消有關", Field.Store.YES));
docs.add(document4);
// 創建文檔對象
Document document5 = new Document();
document5.add(new StringField("id", "5", Field.Store.YES));
document5.add(new TextField("title", "谷歌地圖之父拉斯加盟社交網站Facebook", Field.Store.YES));
docs.add(document5);
// 索引目錄類,指定索引在硬盤中的位置
Directory directory = FSDirectory.open(new File("d:\\indexDir"));
// 引入IK分詞器
Analyzer analyzer = new IKAnalyzer();
// 索引寫出工具的配置對象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
// 設置打開方式:OpenMode.APPEND 會在索引庫的基礎上追加新索引。OpenMode.CREATE會先清空原來數據,再提交新的索引
conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
// 創建索引的寫出工具類。參數:索引的目錄和配置信息
IndexWriter indexWriter = new IndexWriter(directory, conf);
// 把文檔集合交給IndexWriter
indexWriter.addDocuments(docs);
// 提交
indexWriter.commit();
// 關閉
indexWriter.close();
}
查詢索引數據
@Test
public void testSearch() throws Exception {
// 索引目錄對象
Directory directory = FSDirectory.open(new File("d:\\indexDir"));
// 索引讀取工具
IndexReader reader = DirectoryReader.open(directory);
// 索引搜索工具
IndexSearcher searcher = new IndexSearcher(reader);
// 創建查詢解析器,兩個參數:默認要查詢的字段的名稱,分詞器
QueryParser parser = new QueryParser("title", new IKAnalyzer());
// 創建查詢對象
Query query = parser.parse("谷歌");
// 搜索數據,兩個參數:查詢條件對象要查詢的最大結果條數
// 返回的結果是 按照匹配度排名得分前N名的文檔信息(包含查詢到的總條數信息、所有符合條件的文檔的編號信息)。
TopDocs topDocs = searcher.search(query, 10);
// 獲取總條數
System.out.println("本次搜索共找到" + topDocs.totalHits + "條數據");
// 獲取得分文檔對象(ScoreDoc)數組.SocreDoc中包含:文檔的編號、文檔的得分
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
// 取出文檔編號
int docID = scoreDoc.doc;
// 根據編號去找文檔
Document doc = reader.document(docID);
System.out.println("id: " + doc.get("id"));
System.out.println("title: " + doc.get("title"));
// 取出文檔得分
System.out.println("得分: " + scoreDoc.score);
}
}
特殊查詢
public void search(Query query) throws Exception {
// 索引目錄對象
Directory directory = FSDirectory.open(new File("indexDir"));
// 索引讀取工具
IndexReader reader = DirectoryReader.open(directory);
// 索引搜索工具
IndexSearcher searcher = new IndexSearcher(reader);
// 搜索數據,兩個參數:查詢條件對象要查詢的最大結果條數
// 返回的結果是 按照匹配度排名得分前N名的文檔信息(包含查詢到的總條數信息、所有符合條件的文檔的編號信息)。
TopDocs topDocs = searcher.search(query, 10);
// 獲取總條數
System.out.println("本次搜索共找到" + topDocs.totalHits + "條數據");
// 獲取得分文檔對象(ScoreDoc)數組.SocreDoc中包含:文檔的編號、文檔的得分
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
// 取出文檔編號
int docID = scoreDoc.doc;
// 根據編號去找文檔
Document doc = reader.document(docID);
System.out.println("id: " + doc.get("id"));
System.out.println("title: " + doc.get("title"));
// 取出文檔得分
System.out.println("得分: " + scoreDoc.score);
}
}
詞條查詢
/*
* 測試普通詞條查詢
* 注意:Term(詞條)是搜索的最小單位,不可再分詞。值必須是字符串!
*/
@Test
public void testTermQuery() throws Exception {
// 創建詞條查詢對象
Query query = new TermQuery(new Term("title", "谷歌地圖"));
search(query);
}
通配符查詢
/*
* 測試通配符查詢
* ? 可以代表任意一個字符
* * 可以任意多個任意字符
*/
@Test
public void testWildCardQuery() throws Exception {
// 創建查詢對象
Query query = new WildcardQuery(new Term("title", "*歌*"));
search(query);
}
模糊查詢
/*
* 測試通配符查詢
* ? 可以代表任意一個字符
* * 可以任意多個任意字符
*/
@Test
public void testWildCardQuery() throws Exception {
// 創建查詢對象
Query query = new WildcardQuery(new Term("title", "*歌*"));
search(query);
}
數值範圍查詢
/*
* 測試:數值範圍查詢
* 注意:數值範圍查詢,可以用來對非String類型的ID進行精確的查找
*/
@Test
public void testNumericRangeQuery() throws Exception{
// 數值範圍查詢對象,參數:字段名稱,最小值、最大值、是否包含最小值、是否包含最大值
Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
search(query);
}
組合查詢
/*
* 布爾查詢:
* 布爾查詢本身沒有查詢條件,可以把其它查詢通過邏輯運算進行組合!
* 交集:Occur.MUST + Occur.MUST
* 並集:Occur.SHOULD + Occur.SHOULD
* 非:Occur.MUST_NOT
*/
@Test
public void testBooleanQuery() throws Exception{
Query query1 = NumericRangeQuery.newLongRange("id", 1L, 3L, true, true);
Query query2 = NumericRangeQuery.newLongRange("id", 2L, 4L, true, true);
// 創建布爾查詢的對象
BooleanQuery query = new BooleanQuery();
// 組合其它查詢
query.add(query1, BooleanClause.Occur.MUST_NOT);
query.add(query2, BooleanClause.Occur.SHOULD);
search(query);
}
修改索引
/* 測試:修改索引
* 注意:
* A:Lucene修改功能底層會先刪除,再把新的文檔添加。
* B:修改功能會根據Term進行匹配,所有匹配到的都會被刪除。這樣不好
* C:因此,一般我們修改時,都會根據一個唯一不重複字段進行匹配修改。例如ID
* D:但是詞條搜索,要求ID必須是字符串。如果不是,這個方法就不能用。
* 如果ID是數值類型,我們不能直接去修改。可以先手動刪除deleteDocuments(數值範圍查詢鎖定ID),再添加。
*/
@Test
public void testUpdate() throws Exception{
// 創建目錄對象
Directory directory = FSDirectory.open(new File("indexDir"));
// 創建配置對象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
// 創建索引寫出工具
IndexWriter writer = new IndexWriter(directory, conf);
// 創建新的文檔數據
Document doc = new Document();
doc.add(new StringField("id","1",Store.YES));
doc.add(new TextField("title","谷歌地圖之父跳槽facebook ",Store.YES));
/* 修改索引。參數:
* 詞條:根據這個詞條匹配到的所有文檔都會被修改
* 文檔信息:要修改的新的文檔數據
*/
writer.updateDocument(new Term("id","1"), doc);
// 提交
writer.commit();
// 關閉
writer.close();
}
刪除索引
/*
* 演示:刪除索引
* 注意:
* 一般,爲了進行精確刪除,我們會根據唯一字段來刪除。比如ID
* 如果是用Term刪除,要求ID也必須是字符串類型!
*/
@Test
public void testDelete() throws Exception {
// 創建目錄對象
Directory directory = FSDirectory.open(new File("indexDir"));
// 創建配置對象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
// 創建索引寫出工具
IndexWriter writer = new IndexWriter(directory, conf);
// 根據詞條進行刪除
// writer.deleteDocuments(new Term("id", "1"));
// 根據query對象刪除,如果ID是數值類型,那麼我們可以用數值範圍查詢鎖定一個具體的ID
// Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
// writer.deleteDocuments(query);
// 刪除所有
writer.deleteAll();
// 提交
writer.commit();
// 關閉
writer.close();
}