1. 程式人生 > >Lucene搜尋引擎-索引

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才能進行刪除和更新