Lucene搜尋引擎-索引
文章目錄
如果對Lucene不熟悉的,請移步: Lucene搜尋引擎-分詞器
對輸入的一串內容進行分詞以後,如果需要在後續進行檢索,則必須定義如何儲存以及儲存的方式、內容,則這就是索引需要做的事情。
直接上程式碼:
import java.io. File;
import java.io.IOException;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.SortedDocValuesField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
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.BytesRef;
import com.dongnao.lucene.demo.analizer.ik.IKAnalyzer4Lucene7;
public class IndexWriteDemo {
public static void main(String[] args) {
// 建立使用的分詞器,這裡使用IKAnalyzer分詞器
Analyzer analyzer = new IKAnalyzer4Lucene7(true);
// 索引配置物件
IndexWriterConfig config = new IndexWriterConfig(analyzer);
try (
// 索引存放目錄
// 存放到檔案系統中
Directory directory = FSDirectory.open((new File("D:/test/indextest")).toPath());
// 存放到記憶體中
// Directory directory = new RAMDirectory();
// 建立索引寫物件
IndexWriter writer = new IndexWriter(directory, config);
// 準備document
Document doc = new Document();
// 商品id:字串,不索引、但儲存
String prodId = "p0001";
FieldType onlyStoredType = new FieldType();
onlyStoredType.setTokenized(false);
onlyStoredType.setIndexOptions(IndexOptions.NONE);
onlyStoredType.setStored(true);
onlyStoredType.freeze();
doc.add(new Field("prodId", prodId, onlyStoredType));
writer.addDocument(doc);
} catch (IOException e) {
e.printStackTrace();
}
}
}
概念理解
先來理解下概念
- Index:索引,類似傳統資料庫中的表的概念。Lucene的Index可以理解為一個文件收納箱,你可以往內部塞入新的文件,或者從裡面拿出文件,但如果你要修改裡面的某個文件,則必須先拿出來修改後再塞回去。這個收納箱可以塞入各種型別的文件,文件裡的內容可以任意定義,Lucene都能對其進行索引。
- Document:文件,類似傳統資料庫中的行記錄的概念。一個Index內會包含多個Document。寫入Index的Document會被分配一個唯一的ID,即Sequence Number。
- Field:欄位,類似於傳統資料庫中欄位的概念。一個Document會由一個或多個Field組成,Field是Lucene中資料索引的最小定義單位。Lucene提供多種不同型別的Field,例如StringField、TextField、LongFiled或NumericDocValuesField等,Lucene根據Field的型別(FieldType)來判斷該資料要採用哪種型別的索引方式(Invert Index、Store Field、DocValues或N-dimensional等。
- Term和Term Dictionary:詞項和詞典,Lucene中索引和搜尋的最小單位,一個Field會由一個或多個Term組成,Term是由Field經過Analyzer(分詞)產生。Term Dictionary即Term詞典,是根據條件查詢Term的基本索引。
IndexWriter詳解
IndexWriterConfig、Directory、Document作為IndexWriter的輸入
- IndexWriterConfig:索引配置。用這個配置物件建立好IndexWriter物件後,再修改這個配置物件的配置資訊不會對IndexWriter物件起作用。涉及如下配置:
使用的分詞器
如何開啟索引(是新建、還是追加)
還可配置緩衝大小、或緩衝多少個文件,再重新整理到儲存中
還可配置合併、刪除等策略 - Directory:指定索引資料存放的位置,可以存放在記憶體、檔案系統、資料庫中。FSDirectory是存放於檔案系統中,RAMDirectory是存放於記憶體中
注意:IndexWriter是執行緒安全的。若業務程式碼中有其他的同步控制,請不要使用IndexWriter作為所物件,以免死鎖。 - IndexWriter的API使用流程:
IndexWriter writer = new IndexWriter(directory, config);// 建立索引寫物件
writer.addDocument(doc);// 建立document,將文件新增到索引
//writer.deleteDocuments(terms);// 刪除文件
//writer.updateDocument(term, doc);//修改文件
writer.flush();// 重新整理
writer.commit();// 提交
Document詳解
Docement儲存
一個Document相當於資料庫中的表,由多個欄位Field構成,一個Field就像資料庫表中的欄位。
IndexWriter按加入的順序為Document指定一個遞增的id(從0開始),稱為文件id。反向索引中儲存的其實就是這個文件id,正向索引也是這個id,業務資料的主鍵id只是文件的一個欄位。
怎麼又出現了一個正向索引?來,繼續看。
儲存索引的時候其實不僅僅只儲存反向索引,同時也會儲存正向索引,那正向索引儲存的又是什麼呢?舉個例子會比較清晰:
- 百度搜索關鍵字:“新聞標題”
- 搜尋結果有很多內容:新聞標題、新聞摘要、圖片、URL地址
- 關鍵字"新聞標題"是儲存在反向索引中的,而搜尋結果中的其他內容"新聞摘要"、“圖片”、"URL地址"正是儲存在正向索引中的,先通過關鍵字找到對應的document id,在通過id找到具體的結果。
反向索引
詞項 | document id |
---|---|
蒼老師 | {1} |
麻倉優 | {2} |
正向索引
document id | 新聞id | 新聞標題 | 新聞摘要 | URL地址 |
---|---|---|---|---|
1 | 001 | 蒼老師是世界的 | yyy | http://yyy |
2 | 002 | 麻倉優也是世界的 | xxx | http:/xxx |
當搜尋“蒼老師”時,通過document id關聯到新聞資訊,而更具體的新聞資訊則是儲存在資料庫或其他地方的。
上述的每個資訊統稱為欄位“Field”,有欄位名name、欄位值value、欄位型別type三部分構成。欄位值可以是文字、二進位制或數值。
看完上述例子後應該對以下的問題會非常清楚:
- 新聞:新聞id,新聞標題、新聞內容、作者、所屬分類、發表時間*
- 網頁搜尋的結果:標題、內容、連結地址*
- 商品:id、名稱、圖片連結、類別、價格、庫存、商家、品牌、月銷量、詳情*
- 我們收集資料建立document物件來為其建立索引,資料的所有屬性是否都需要加入到document中?如資料庫表中的資料記錄的所有欄位是否都需要放到document中?哪些欄位應加入到document中?是不是所有加入的欄位都需要進行索引?是不是所有加入的欄位都要儲存到索引庫中?什麼樣的欄位該被索引?什麼樣的欄位該被儲存?
Field索引型別
實際應用中會碰到要在搜尋結果中做關鍵字高亮,實現短語
查詢、臨近查詢(跨度查詢)等等,那這些資訊又是如何儲存的呢?比如:要搜尋包含“張三” “李四”,且兩詞之間跨度不超過5個字元。
這種時候就需要用到詞項向量。
詞項向量:一個欄位分詞器分詞後,每個詞項會得到一系列屬性資訊,如 出現頻率、位置、偏移量等,這些資訊構成一個詞項向量 termVectors
可以在Field的索引型別中設定是否儲存這些此項向量
IndexOptions索引選項:
NONE:不索引
DOCS:反向索引中只儲存了包含該詞的 文件id
DOCS_AND_FREQS:反向索引中會儲存 文件id、詞頻
DOCS_AND_FREQS_AND_POSITIONS:反向索引中儲存 文件id、詞頻、位置
DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS:反向索引中儲存 文件id、詞頻、位置、偏移量
其實為了提升反向索引的效率,這樣的欄位的位置、偏移資料是不應該儲存到反向索引中的,這也是為什麼 IndexOptions為什麼有那些選項的原因。在lucene4.0以前,反向索引中總會儲存這些資料,4.0後改進為可選擇的。
附加資訊Payloads
對於不需要在搜尋反向索引時用到,但在搜尋結果處理時需要的位置、偏移
量等欄位,我們可以單獨為該欄位儲存(文件id–>詞項
向量)的正向索引,即附加資訊Payloads。
FieldType實現類中有對應的set方法:
boolean storeTermVectors() 是否儲存詞項向量
boolean storeTermVectorPositions() 是否在詞項向量中儲存位置
boolean storeTermVectorOffsets() 是否在詞項向量中儲存偏移量
boolean storeTermVectorPayloads() 是否在詞項向量中儲存附加資訊
支援排序
我們往往需要對搜尋的結果支援按不同的欄位進行排序,如商品搜尋
結果按價格排序、按銷量排序等。以及對搜尋結果進行按某欄位分組統計,如
按品牌統計。
空間換時間:對這種需要排序、分組、聚合的欄位,為其建立獨立的文件->欄位值的正向索引、列式儲存。這樣我們要載入搜中文件的這個欄位的資料就快很多,耗記憶體少。
IndexableFieldType 中的 docValuesType方法 就是讓你來為需要排序、分組、
聚合的欄位指定如何為該欄位建立文件->欄位值的正向索引的。
DocValuesType選項如下:
- NONE 不開啟docvalue
- NUMERIC 單值、數值欄位,用這個
- BINARY 單值、位元組陣列欄位用
- SORTED 單值、字元欄位用, 會預先對值位元組進行排序、去重儲存
- SORTED_NUMERIC 單值、數值陣列欄位用,會預先對數值陣列進行排序
- SORTED_SET 多值欄位用,會預先對值位元組進行排序、去重儲存
具體使用選擇:
- 字串+單值 會選擇SORTED作為docvalue儲存
- 字串+多值 會選擇SORTED_SET作為docvalue儲存
- 數值或日期或列舉欄位+單值 會選擇NUMERIC 作為docvalue儲存
- 數值或日期或列舉欄位+多值 會選擇SORTED_SET作為docvalue儲存
注意:
- DocValuesType是強型別要求的,欄位的值必須保證同類型。
- 需要排序、分組、聚合、分類查詢(面查詢)的欄位才建立docValues
Lucene所有欄位子類
Lucene內部提供了很多Field的子類,可以根據實際情況進行靈活選擇。
- 如果單個Field無法滿足需求,還可以進行多個Field組合。
- 如果連組合都滿足不了,還可以直接用Filed+FiledType的方式
Luke索引檢視工具
下載地址:https://github.com/DmitryKey/luke/releases
當前最新版本7.3.1,可用於Lucene7.3.0版本
該工具可以用來檢視我們建立索引後的結果。
索引更新
主要使用IndexWriter的API方法進行更新
- 刪除流程:根據Term(詞項即欄位)、Query找到相關的文件id、同時刪除索引資訊,再根據文件id刪除對應的文件儲存
- 更新流程:先刪除、再加入新的doc
- 注意:只能根據有所有的Term才能進行刪除和更新