日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

lucene7.5的数据结构

發布時間:2024/2/28 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 lucene7.5的数据结构 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 2. lucene的數據結構
      • 2.1 索引的邏輯層次
      • 2.2 lucene 存儲的數據概述
        • 2.2.1 lucene的存儲模塊
        • 2.2.2 lucene的一個index的文件
        • 2.2.3 lucene 的數據類型
        • 2.2.4 ducoment number
      • 2.3 正向信息的存儲
        • 2.3.1 .fnm 文件
          • 2.3.1.1 一級數據
          • 2.3.1.2 二級數據Field
        • 2.3.2 XXX.fdx,XXX.fdt文件
          • 2.3.2.1 .fdt 文件
          • 2.3.2.2 .fdx 文件
        • 2.3.3 XXX.tvx,XXX.tvd
        • 2.3.4 .nvd,.nvm
          • 2.3.4.1 nvd中到底存儲了什么
          • 2.3.4.2 .nvd文件
          • 2.3.4.3 .nvm文件
          • 2.3.4.4 根據DocId查找norm的過程簡述
      • 2.4 反向索引信息的存儲
        • 2.4.1 XXX.tim,XXX.tip
          • 2.4.1.1 XXX.tip
          • 2.4.1.1 XXX.tim
        • 2.4.2 XXX.doc
        • 2.4.3 XXX.pos文件
        • 2.4.4 XXX.pay文件
        • 2.4.5 在倒排中的查詢邏輯
      • 2.5 docvalues信息的存儲 .dvd .dvm
      • 2.6 多維數值類信息point的存儲 .dim .dii
    • 待處理問題

2. lucene的數據結構

2.1 索引的邏輯層次

在lucene中索引存儲的邏輯層次有多個層次,從大到小依次是

  • index:索引代表了一類數據的完整存儲
  • segment: 一個索引可能有一個或者多個段構成
  • doc: segment中存儲的是一篇一篇的文檔doc,每個segment是一個doc的集合
  • field: 每個doc都有多個field構成,filed才包含了具體的文本,類似于一個json對象的一個屬性
  • term: 每個field的值可以進行分詞,進而得到多個term,term是最基本的單元,每個field可以保存自己的詞向量,用來計算搜索相似度。
  • 2.2 lucene 存儲的數據概述

    2.2.1 lucene的存儲模塊

    在lucene中,因為lucene是一個存儲系統,所以他的首要任務是以一定的數據結構將輸入進來的數據(doc)保存起來,在檢索的時候才能將這些信息返回給用戶
    同時,為了能夠支撐高效的檢索,還要有額外的數據結構來支撐高效檢索
    為了支撐高效檢索,lucene創造了倒排索引,同時,為了能夠滿足更加復雜的查詢,比如按照某一個字段進行排序等功能,lucene又在3.x引入了docvalues 正排索引的概念,為了支持數值查詢或者地理位置查詢等多維度的查詢,lucene使用了BKD-Tree,來支撐數值型的range查詢。
    綜上,目前lucene的數據結構模塊包括

  • 文檔存儲信息:保存了doc的信息,是從doc–>term的信息維度
  • 反向信息:倒排索引,保存了從term—>doc_id 的信息
  • docvalues : 保存了doc_id—> field的信息,方便根據doc_id快速檢索某個field
  • point-value: 數值類型信息,可以是多維的,比如地理位置信息等,可以進行多維數值數據的范圍查詢
  • 2.2.2 lucene的一個index的文件

    基于lucene7.5 打開對應的文件夾,看看lucene的一個索引中包含哪些文件

    NameExtensionBrief Description中文
    Segments Filesegments_NStores information about a commit point存儲一次commit point信息
    Lock Filewrite.lockThe Write lock prevents multiple IndexWriters from writing to the same file.寫鎖,防止多個indxer-writer寫同一個文件
    Segment Info.siStores metadata about a segment保存一個segment的元數據信息,lucene版本,segment內的doc-count
    Compound File.cfs, .cfeAn optional “virtual” file consisting of all the other index files for systems that frequently run out of file handles.復合文件,可以沒有,就是多個索引的一些信息都存儲在里面,分析數據結構的時候可以先不關注者一點
    Fields.fnmStores information about the fields保存doc的field的元信息
    Field Index.fdxContains pointers to field data.fdt的索引文件
    Field Data.fdtThe stored fields for documents存儲每個doc的具體的內容,按照doc-numer分組,每組存儲了一個doc的field信息
    Term Dictionary.timThe term dictionary, stores term info保存term的詞典
    Term Index.tipThe index into the Term Dictionaryterm詞典的索引文件
    Frequencies.docContains the list of docs which contain each term along with frequency詞典中的每個term指向的列表,每一項包含了docId+term-frequency信息(詞頻信息)
    Positions.posStores position information about where a term occurs in the index倒排保存term在field中經過tekenAnalizer后的term所在的位置,是第幾個term
    Payloads.payStores additional per-position metadata information such as character offsets and user payloads詞典中的每個term指向的列表,每一項都包含了文檔的元數據信息或者用戶自定義的一些信息
    Norms.nvd, .nvmEncodes length and boost factors for docs and fields長度歸一化,doc-boost, field-boost的記錄信息
    Per-Document Values.dvd, .dvmEncodes additional scoring factors or other per-document information.保存了額外的排序因子,或者是每個文檔獨有的信息
    Term Vector Index.tvxStores offset into the document data file.tvd的索引文件
    Term Vector Data.tvdContains term vector data.保存了每個doc的每個field的term vector
    Live Documents.livInfo about what documents are live只有在一個segment中包含被刪除的文檔時才會生成,它記錄了當前段中沒有被刪除的文檔號
    Point values.dii, .dimHolds indexed points, if any存儲point類型的數據,int,long等

    2.2.3 lucene 的數據類型

  • Byte:是最基本的類型,長8位(bit)。
  • UInt32:由4個Byte組成。
  • UInt64:由8個Byte組成。
  • VInt:
    變長的整數類型,它可能包含多個Byte,對于每個Byte的8位,其中后7位表示數值,最高1位表示是否還有另一個Byte,0表示沒有,1表示有。
    越前面的Byte表示數值的低位,越后面的Byte表示數值的高位。
    例如130化為二進制為 1000, 0010,總共需要8位,一個Byte表示不了,因而需要兩個Byte來表示,第一個Byte表示后7位,并且在最高位置1來表示后面還有一個Byte,所以為(1) 0000010,第二個Byte表示第8位,并且最高位置0來表示后面沒有其他的Byte了,所以為(0) 0000001。
  • Chars:是UTF-8編碼的一系列Byte。
  • String:一個字符串首先是一個VInt來表示此字符串包含的字符的個數,接著便是UTF-8編碼的字符序列Chars。
  • 2.2.4 ducoment number

    在開始正式了解各個文件之前,我們要說一下ducoment number 的概念
    數據庫內通過主鍵來唯一標識一行,而Lucene的Index通過DocId來唯一標識一個Doc。這個DocId也就是document number
    不過有幾點要特別注意:

  • DocId實際上并不在Index內唯一,而是Segment內唯一,Lucene這么做主要是為了做寫入和壓縮優化。那既然在Segment內才唯一,又是怎么做到在Index級別來唯一標識一個Doc呢?方案很簡單,Segment之間是有順序的,舉個簡單的例子,一個Index內有兩個Segment,每個Segment內分別有100個Doc,在Segment內DocId都是0-100,轉換到Index級的DocId,需要將第二個Segment的DocId范圍轉換為100-200。
  • DocId在Segment內唯一,取值從0開始遞增。但不代表DocId取值一定是連續的,如果有Doc被刪除,那可能會存在空洞。
  • 一個文檔對應的DocId可能會發生變化,主要是發生在Segment合并時。
  • Lucene內最核心的倒排索引,本質上就是Term到所有包含該Term的文檔的DocId列表的映射。所以Lucene內部在搜索的時候會是一個兩階段的查詢,第一階段是通過給定的Term的條件找到所有Doc的DocId列表,第二階段是根據DocId查找Doc。Lucene提供基于Term的搜索功能,也提供基于DocId的查詢功能。就是倒排索引和正排信息共同支撐了整個查詢過程。

    2.3 正向信息的存儲

  • 按層次保存了從索引,一直到詞的包含關系:索引(Index) –> 段(segment) –> 文檔(Document) –> 域(Field) –> 詞(Term)
  • 也即此索引包含了那些段,每個段包含了那些文檔,每個文檔包含了那些域,每個域包含了那些詞。
  • 既然是層次結構,則每個層次都保存了本層次的信息以及下一層次的元信息,也即屬性信息,比如一本介紹中國地理的書,應該首先介紹中國地理的概況,以及中國包含多少個省,每個省介紹本省的基本概況及包含多少個市,每個市介紹本市的基本概況及包含多少個縣,每個縣具體介紹每個縣的具體情況。
    包含正向信息的文件有:
    • segments_N保存了此索引包含多少個段,每個段包含多少篇文檔。
    • XXX.fnm保存了此段包含了多少個域,每個域的名稱及索引方式。
    • XXX.fdx,XXX.fdt保存了此段包含的所有文檔,每篇文檔包含了多少域,每個域保存了那些信息。
    • XXX.tvx,XXX.tvd 保存了此段包含多少文檔,每篇文檔包含了多少域,每個域包含了多少詞,每個詞的字符串,位置等信息。

    因為segments_N信息含量相對較少,我們就不再具體介紹,看看其他幾個文件都存儲了什么吧。

    2.3.1 .fnm 文件

    對應的官方文檔的信息參考這里

    Header,FieldsCount, [{FieldName,FieldNumber, FieldBits,DocValuesBits,DocValuesGen,Attributes}]*FieldsCount FieldsCount,Footer
    2.3.1.1 一級數據
  • Header: 一些元信息是fnm文件的版本號,對于Lucene 2.9為-2
  • FieldsCount: 域的數目
  • 一個數組的域(Fields),包含下面的信息
  • 2.3.1.2 二級數據Field

    一個filed包含的信息:

  • FieldName:域名,如"title",“modified”,"content"等。
  • FieldNumber: 不像之前的版本那樣通過field的順序來隱含的表達fieldNumber,這里直接給了每個field一個number
  • FieldBits: 總共占用一個字節,一系列標志位,表明對此域的索引方式
  • 倒數第一位:只對索引field有效,1表示保存詞向量,0為不保存詞向量。
    對于長度較小的字段不建議開啟term verctor,因為只需要重新做一遍分詞即可拿到term信息,而針對長度較長或者分詞代價較大的字段,則建議開啟term vector。Term vector的用途主要有兩個,一是關鍵詞高亮,二是做文檔間的相似度匹配(more-like-this)。
  • 倒數第二位:只對index field有效,1表示不保存標準化因子,0則是保存標準化因子
  • 倒數第三位:是否保存payload,1保存,0不保存
  • IndexOptions: 一個字節,用來指導index相關的設置
    0: not indexed 不進行索引
    1: indexed as FieldInfo.IndexOptions.DOCS_ONLY 索引文檔編號
    2: indexed as FieldInfo.IndexOptions.DOCS_AND_FREQS 索引文檔編號、關鍵詞頻率
    3: indexed as FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS 索引文檔編號、關鍵詞頻率、位置
    4: indexed as FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS 索引文檔編號、關鍵詞頻率
  • DocValuesBits:一個字節,存放docvalue相關的信息,分為兩個4位,其中高4個bit用來描述是否記錄norm,低4個bit用來描述DocValues類型,DocValues的類型包括以下類型:
    0:NONE
    1:NUMERIC
    2:BINARY
    3:SORTED
    4:SORTED_SET
    5:SORTED_NUMERIC
  • Attributes
    該字段描述了存儲當前域的索引文件的格式(format),比如說當前是一個DocValues的域,那么Attributes的字段會有下面的值:
    PerFieldDocValuesFormat.format:Lucene70
    表示使用Lucene70這種格式來生成索引文件.dvd、.dvm。
  • 4.0以后Field.Index廢棄,使用FieldType來進行設置,并通過setIndexOptions方法設置索引選項
    StringField 不分詞并索引的字段,搜索時整字段匹配
    TextField 分詞并索引
    StoredField 僅保存,不索引

    FieldType type = new FieldType(); type.setIndexed(true); type.setStored(true); type.setIndexOptions(FieldInfo.IndexOptions.DOCS_AND_FREQS); indexableFields.add(new Field("content", content, type));

    這里只是api層面的變化,實際的存儲結構并沒有變化。

    2.3.2 XXX.fdx,XXX.fdt文件

    這兩個信息存儲了segment中文檔的原始信息

    2.3.2.1 .fdt 文件

    .fdt存儲了當前segment中所有doc的field信息,是按照documnet-number來分組的。在新的文檔中,因為存儲上壓縮的考慮等,信息不是很容易理解。
    fdt中的信息是按照Chunk 來進行組織的,每128個doc組成一個chunk,每個chunk包含 DocBase,ChunkDoc,DocFieldCounts,DocLengths,CompressedDocs

  • DocBase: 當前chunk中第一個文檔的文檔號,因為根據這個文檔號來差值存儲,在讀取的階段需要根據該值恢復其他文檔號。
  • ChunkDocs: 當前chunk中的doc數量
  • DocFieldCounts:當前chunk中所有doc的field-num
  • DocLengths: 每篇文檔的長度
  • CompressedDocs: 存儲了真正的所有doc的field信息
  • CompressedDocs 中的數據結構

  • CompressedDocs 中存儲了一個[Doc] 數組,數組的長度為 ChunkDocs
  • Doc中的數據結構
  • 域的編號filed number ,和.fnm中的field number對應
  • 域值的類型:String、BinaryValue、Int、Float、Long、Double
  • 域值的編號跟域值的類型組合存儲為FieldNumAndType
  • Value:域值
    通過這里也可以看出,每個doc的信息就得到了存儲。但是如果知道了一個文檔號,想要獲取這個文檔的內容,可能要遍歷這些chunk,則是很慢的一個操作,所以lucene增加了一個.fdx文件來做為索引文件,加快根據docId提取文檔內容的過程。
  • 2.3.2.2 .fdx 文件

    .fdx中存儲的主要是一個Block列表,每當.fdt文件中生成1024個chunk的時候便會在.fdx中生成一個Block。
    Block的數據分為三個主要部分:

  • BlockChunks :block中包含的chunk的個數,即1024個
  • DocBases:存儲了block中的每個chunk的文檔號信息,但是不是列表形式的,而是使用了差值來壓縮存儲
  • DocBase: block中第一個文檔的文檔號。用來在讀取階段,恢復所有chunk中其他被編碼的文檔號。
  • AvgChunkDocs: block中平均一個chunk中包含的文檔數,因為block是等所有chunk都產生的時候才會產生的block,所以avg是可以求出的
  • BitsPerDocBaseDelta: 描述了存儲文檔號的需要的bit個數。
  • DocBaseDeltas: 數組,使用差值存放了每個chunk中的文檔數,結合DocBase,AvgChunkDocs 可以很快的算出block中第n個chunk的第一個文檔號doc number,第n個chunk的doc base = DocBase+n*AvgChunkDocs+DocBaseDeltas[n]
  • 上面的信息主要是為了快速定位一個docId為k的doc所在的chunck編號n,有了chunck編號n,結合下面的StartPointers就可以在.fdt文件中定位chunk,進而快速找到doc的內容。
  • StartPointers: 存儲了block中的每個chunk在.fdt文件中的位置
  • StartPointerBase:??當前block中第一個chunk的在.fdt中的位置。
  • AvgChunkSize:??block中平均每一個chunk的大小。
  • BitsPerStartPointerDelta:??存儲每一個chunk大小需要固定bit個數。
  • StartPointerDeltas:??邏輯跟DocBaseDeltas一樣,第n個chunk 的pointer= StartPointerBase + AvgChunkSize * n + StartPointerDeltas[n]
  • 正常的一個工作邏輯是,我們有了一個docId,

  • 首先.fdx的信息加載到內存中,根據每個Block的DocBases 進行二分查找,可以得到該docId屬于哪個Block
  • 將該Block中的的DocBases進行重構,可以得到每個chunk的DocBase構成的一個數組,再使用二分查找,可以得到chunk的編號n
  • 根據第n個chunk 的pointer= StartPointerBase + AvgChunkSize * n + StartPointerDeltas[n]可以得到該doc在.fdt中的chunk的初始位置
  • 根據chunk的DocBase,DocLengths等得到CompressedDocs的位置,即可得到doc的信息
  • 2.3.3 XXX.tvx,XXX.tvd

    參考這里
    .tvd 存儲的是每個doc的每個field的term vector,按照doc進行分組, .tvx 是.tvd的索引文件。
    .tvd文件存儲了每個doc的每個filed的terms,term 對應的frequencies, positions, offsets 信息。
    .tvd文件中主要的數據結構類似.fdt文件,是由一個個chunk組成的,但是chunk中的數據結構略有不同。
    每個chunk中的結構如下:

  • DocBase: DocBase是chunk中第一個文檔的文檔號。
  • ChunkDocs: chunk中包含的文檔個數。
  • NumFields: NumFields記錄了每篇文檔中存儲域的個數,使用了一個DocNumFields 數組來進行記錄每個doc的field的個數
  • FieldNums:是一個數組,存儲了chunk中所有的filed的編號(不是每個doc的,是filed去重后得到的所有的filed的編號,不會大于.fnm中filed的編號個數)
  • FieldNumOffs: [FieldNumOff]*TotalFields FieldNumOffs想要存儲的是當前chunk中每個doc的所擁有的list[Field-Number]
  • FieldNumOff 存儲的是每個Field的Field-Number,實際上存儲的是FieldNums中的數組的下標
  • TotalFields= chunk中所有的doc的所有field的數量的和= sum(NumFields)
  • Flags: Flags用來描述域是否存放位置position、偏移offset、負載payload信息,flag的值可以是下面3個值的組合:
    0x01:包含位置position信息
    0x02:包含偏移offset信息
    0x04:包含負載payload信息
  • TermData: 真正存儲了當前chunk中所有doc的term信息,并沒有假如doc的標識信息,需要沖前面的信息中解析。里面的數據結構相對比較多
  • NumTerms: NumTerms描述了每一個域包含的term個數,使用PackedInts存儲。
  • TermLengths: TermLengths描述了每一個域中的每一個term的長度,使用PackedInts存儲。
  • TermFreqs: TermFreqs描述了每一個域中的每一個term在當前文檔中的詞頻,使用PackedInts存儲。
  • Positions: Positions描述了每一個域中的每一個term在當前文檔中的所有位置position信息,使用PackedInts存儲。
  • StartOffset: StartOffset描述了每一個域中的每一個term的startoffset,使用PackedInts存儲。
  • Lengths: Lengths描述了每一個域中的每一個term的偏移長度,使用PackedInts存儲。
  • TermAndPayloads: 使用LZ4算法存儲每一個域中的每一個term值跟payload(如果有的話)。
  • 2.3.4 .nvd,.nvm

    .nvm文件保存索引字段加權因子的元數據,.nvd文件保存索引字段加權數據

    2.3.4.1 nvd中到底存儲了什么

    關于nvd文件中具體存儲了什么,一度非常疑惑,感覺lucene并沒有直接說清楚,后面自己想到,norm是打分排序的一個歸一化因子組合,不同的相似度計算算法可能需要是不一樣的。
    可以參考這個問題
    比如之前的金典的TF/IDF算法對norm的需求是:

  • Document boost:此值越大,說明此文檔越重要。
  • Field boost:此域越大,說明此域越重要。
  • lengthNorm(field) = (1.0 / Math.sqrt(numTerms)):一個域中包含的Term 總數越多,也即文檔越長,此值越小,文檔越短,此值越大。
  • 由此想到norm應該會因為你選擇了不同的相似度計算算法而不一樣。在網上也翻閱到,lucene確實有很多norm的計算方式

    abstract long Similarity.computeNorm(FieldInvertState state) Computes the normalization value for a field, given the accumulated state of term processing for this field (see FieldInvertState).long BM25Similarity.computeNorm(FieldInvertState state) long MultiSimilarity.computeNorm(FieldInvertState state) long PerFieldSimilarityWrapper.computeNorm(FieldInvertState state) long SimilarityBase.computeNorm(FieldInvertState state) Encodes the document length in the same way as TFIDFSimilarity.long TFIDFSimilarity.computeNorm(FieldInvertState state) float DefaultSimilarity.lengthNorm(FieldInvertState state) Implemented as state.getBoost()*lengthNorm(numTerms), where numTerms is getLength() if DefaultSimilarity.setDiscountOverlaps(boolean) is false, else it's getLength() - getNumOverlap().abstract float TFIDFSimilarity.lengthNorm(FieldInvertState state) Compute an index-time normalization value for this field instance.

    所以nvd中是一個開放式的多個byte的數據結構,各路開發者可以按照自己喜歡的方式來產生和解析norm即可

    2.3.4.2 .nvd文件

    因為norm的信息主要是基于域進行設置的,所以他的統計維度是按照field進行統計的。就是統計了某個field每個doc在該field下的norm值
    .nvd文件的主體內容是一個數組 [FieldData]*NumFields ,這里的NumFields 是指Field的數量,應該是指text的field的數量
    FieldData的內容是:

  • DocsWithFieldData: 記錄了包含該field的docIdList(實際上根據不同情況有優化,但是主要要表達的就是這個意思)
  • 優化一,當所有的doc都含有該field的話,對應的這里就不需要存儲了,在.nvm中直接標識一下就行了
  • 優化二,當所有的doc都不含有該field的話,這個地方也不需要進行存儲了,在.nvm中直接標識一下就可以了
  • NormsData: 記錄了具體的norms,是一些列的byte,沒有具體的分界,使用的時候是通過.nvm的索引進行取用和重建的
  • 2.3.4.3 .nvm文件

    .nvm文件保存索引字段加權因子的元數據。
    .nvm的數據結構主要是一個數組的Entry,[Entry]*NumFields
    Entry中的結構如下

  • FieldNumber: 域的編號,用來唯一標識一種域。
  • DocsWithFieldAddress: 指向了.nvd的DocsWithFieldData開始處,但是我們談到了在.nvd中DocsWithFieldData有兩點優化,所以在這里也有對應的改變
  • 優化一,當所有的doc都含有該field的話,這里存儲的是-1
  • 優化二,當所有的doc都不含有該field的話,這里存儲的是-2
  • DocsWithFieldLength: .nvd中的DocsWithFieldData的長度,這里同樣適用DocsWithFieldAddress的兩個優化
  • 優化一,當所有的doc都含有該field的話,這里存儲的是0
  • 優化二,當所有的doc都不含有該field的話,這里存儲的是0
  • NumDocsWithField: NumDocsWithField描述了包含當前域的文檔個數。
  • BytesPerNorm: 找出當前域在所有所屬文檔中的最大跟最小的兩個標準化值來判斷存儲一個標準化值最大需要的字節數
  • NormsAddress: NormsAddress作為索引映射nvd中一塊數據區域,指向了.nvd中的NormsData這塊數據區域的開始處,即當前域在所有文檔中的標準化值。
  • 2.3.4.4 根據DocId查找norm的過程簡述

    因為norm是在相似度計算階段適用,所以是在通過query在term詞典中過濾得到了倒排DocIdlist之后的事情。
    有了TargetDocId,和query適用的Field信息,

  • 通過.nvm中的信息快速的找到對應的Entry,根據FieldNumber來找Entry,實際上Entry不會太多
  • 判斷DocsWithFieldAddress,根據其不同情況來找到DocId的Field對應的NormsData
  • 當所有的doc都含有該field的話,這里存儲的是-1
  • 因為docId是連續排列的,所以只要知道當前segment的第一個docId就可以知道要查詢的TargetDocId對應的Norms數據在.nvd的NormsData中是第幾個,假設順序為第n個
  • NormsAddress+n*BytesPerNorm 即可得到TargetDocId對應的norms在.nvd 的NormsData中對應的位置
  • 部分doc含有該field的話,這種情況理論上應該避免,因為這會導致比較大的查詢損耗比較大,也就是一個doc的field有,所有的都有才合適
  • 根據DocsWithFieldAddress找到.nvd文件中的DocsWithFieldData的起始位置,然后根據DocsWithFieldLength讀出 DocsWithFieldData 得到docIdList,進而二分查找找到TargetDocId對應的位置n
  • NormsAddress+n*BytesPerNorm 即可得到TargetDocId對應的norms在.nvd 的NormsData中對應的位置
  • 可以看出來第一步比較耗時
  • 到此,正向索引的信息基本上是描述完了,接下來就是看看反向的信息有哪些了。

    2.4 反向索引信息的存儲

    保存了詞典到倒排表的映射:詞(Term) –> 文檔(Document)
    包含反向信息的文件有:

    • XXX.tim,XXX.tip保存了詞典(Term Dictionary),也即此段包含的所有的詞按字典順序的排序。
    • XXX.doc保存了倒排表,也即包含每個詞的文檔ID列表。
    • XXX.pos保存了倒排表中每個詞在包含此詞的文檔中的位置。
    • XXX.pay保存倒排表中的每個詞在包含該詞的文檔中的offset和payload信息
      反向索引信息就是我們常說的倒排表,也是實現文本近似查詢的關鍵支撐。

    2.4.1 XXX.tim,XXX.tip

    在lucene7.5中對倒排表的term dictionary (詞典)部分進行了比較大的改動。之前是使用跳躍表來實現的有序term的快速查詢?,F在改進到了FST(Finite State Transducer)有限狀態轉換機。FST的具體實現相對比較復雜,我們這里用類比的方式解釋一下。FST其實和AC自動機很像。AC自動機是基于Trie-Tree的有限狀態自動機,目的是為了實現多模式的字符串匹配。Trie樹可以實現多個模式串的前綴壓縮,后綴suffix是開放的結構,lucene為了進一步節約存儲空間使用FST,FST基于Trie樹更進一步,對后綴也做了壓縮,整個數據結構類似于一個盜夢空間中的陀螺,中間粗大,兩端都收于一點,而且查詢效率也很高。前綴查詢也很easy。

    2.4.1.1 XXX.tip

    XXX.tip部分存儲的是FST,而且tip為每個filed都存儲了一個FST結構。
    總體結構 NumFields NumFields, DirOffset

  • FSTIndex: 每個field都會有一個一個FST結構,因為我們的查詢常常是指定field的
  • IndexStartFP: vlong類型,每個field都對應有一個IndexStartFP,指明了每個field的FSTIndex 在tip文件中的位置,方便快速查找某個field的FSTIndex
  • DirOffset: vlong類型,第一個IndexStartFP的位置
  • 這樣的話就可以通過DirOffset找到第一個IndexStartFP的位置,然后按照field-number就可以找到對應的IndexStartFP,這個應該很短,就是NumFields 個vlong。然后再通過IndexStartFP找到對應的FSTIndex.

    這里需要強調的一點是,FST并不存儲term的全部數據,而是term的前綴信息,比如有terms: abc ,abe, abfg 那么FST存儲的是ab, 也就是他們的公共前綴,剩下的c,e,fg等存儲在FST指向的XXX.tim文件當中。這樣也避免了FST過大,同時又能夠有比較好的查詢效果。

    2.4.1.1 XXX.tim

    .tim 的主要結構 NumBlocks, FieldSummary, DirOffset,

  • NodeBlock: 這個是term后綴存儲的時候按照塊存儲的基本單位。在這里理論上也不需要再有屬于哪個field的標識,因為.tip中的FST索引是按照Field來進行區分的,而FST又會指向.tim文件,也就間接完成了field的區分。NodeBlock分為兩種,OuterNode | InnerNode;
  • OuterNode : EntryCount, SuffixLength, SuffixLength, StatsLength, EntryCount, MetaLength, EntryCount
  • EntryCount:??EntryCount描述了當前的OuterNode中包含多個entries,即包含了多少個term的信息。

  • SuffixLength、StatsLength、MetaLength:??這三個值分別描述了所有term的Suffix、TermStats、TermMetadata在.tim文件中的數據長度,在讀取.tim時用來確定讀取Suffix、TermStats、TermMetadata的范圍區間。

  • SuffixLength: 這是一個字節序列,實際上會被解析成一個suffix數組,數組中每一個元素被分為兩個部分,length,SuffixValue

  • Length:??term的后綴長度。
  • SuffixValue:??term的后綴值,之前提到按照term的大小順序進行處理的,如果一批term具有相同的前綴并且這批term的個數超過25個,那么這批term會被處理為一個NodeBlock,并且SuffixValue只存儲除去相同前綴的后綴部分。
  • TermStats: TermStats中又包含了兩個元素DocFreq和TotalTermFreq

  • DocFreq:??DocFreq描述了包含當前term的文檔個數。
  • TotalTermFreq:??TotalTermFreq描述了term在文檔中出現的總數,實際存儲了與DocFreq的差值,目的是盡可能壓縮存儲,即使用差值存儲。
  • TermMetadata: TermMetadata中包含的信息比較多,所有和外部關聯的信息都在這里,比如指向.doc,.pos , .pay文件的指針

  • SingletonDocID:??如果只有一篇文檔包含當前term,那么SingletonDocID被賦值這篇文檔號,如果不止一篇文檔包含當前term,那么SingletonDocID不會寫入到.tim文件中。
  • SkipOffset:??SkipOffset用來描述當前term信息在.doc文件中 跳表信息的起始位置。
  • DocStartFP:??DocStartFP是當前term信息在.doc文件中的起始位置。
  • PosStartFP:??PosStartFP是當前term信息在.pos文件中的起始位置。
  • LastPosBlockOffset:??如果term的詞頻大于BLOC_SIZE,即大于128個,那么在.pos文件中就會生成一個block,LastPosBlockOffset記錄最后一個block結束位置,通過這個位置就能快速定位到term的剩余的position信息,并且這些position信息的個數肯定是不滿128個.
  • PayStartFP:??payStartFP是當前term信息在.pay文件中的起始位置。
  • InnerNode: 這個是為了保存哪些已經在FST中存在的term了,也就是不要后綴的term,結構類似OuterNode
  • FieldSummary:對tim文件中的field進行描述,相當于一些統計信息和元信息吧
  • NumFields:??NumFields描述了.tim文件中的有多少種域。
  • FieldNumber:??FieldNumber記錄了當前域的編號,這個編號是唯一的,同時它是從0開始的遞增值。數值越小,說明該域更早的被添加進了索引。
  • NumTerms:??NumTerms記錄了當前域中有多少種term。
  • RootCodeLength:??RootCodeLength描述了當前域中的term的FST數據的長度。
  • RootCodeValue:??RootCodeValue描述了當前域中的term的FST數據。
  • SumTotalTermFreq:??sumTotalTermFreq描述了當前域中所有term在文檔中的總詞頻。
  • SumDocFreq:??SumDocFreq描述了包含當前域中的所有term的文檔數量。
  • DocCount:??DocCount描述了有多少篇文檔包含了當前域。
  • LongsSize:??longsSize的值只能是1,2,3三種,1說明了當前域只存儲了doc、frequency,2說明了存儲了doc、frequency,positions,3說明存儲了doc、frequency,positions、offset。
  • MinTerm:??當前域中的最小的term。
  • MaxTerm:??當前域中的最大的term。
  • 2.4.2 XXX.doc

    .doc文件存儲的是倒排表中的每個term指向的DocIdList,當然,還有term在當前doc中出現的次數
    主體結構為 <TermFreqs, SkipData?>TermCount
    有一個數組的<TermFreqs, SkipData?>列表,大小為term的總數,所以這個文件還是有一定的大小的。

  • TermFreqs: 存儲了docId和term在該doc中出現的次數
  • SkipData: 存儲了docId的跳躍表:為什么需要跳躍表呢,假設這樣的一個場景,我們需要合并兩個term查出來的docIdList,假如兩個docIdList都很長,但是相同的只有幾個docId,這樣的話如果是兩個鏈表遍歷求交的話就會很慢,但是使用跳躍表進行求交的話就會快很多了。
  • 2.4.3 XXX.pos文件

    .pos文件存儲的主要是term在單個文檔中的位置信息。有時候為了加快查詢速速,也會存儲一些payload信息和offset信息。
    但是大部分場景實際上只需要positions信息就夠了,就不會去加載.pay文件了,只需要加載.pos文件即可
    主體結構是這樣: TermCount
    TermPositions:單個TermPositions存儲了該term在每個doc的中的position信息,TermPositions之間按照term進行排序,TermPositions內部按照docId進行排序。

    2.4.4 XXX.pay文件

    .pay文件中主要存儲的是payload信息和offset信息
    主體結構是<TermPayloads, TermOffsets?> TermCount
    TermPayloads: 單個TermPayloads存儲了該term在每個doc中的payload信息,順序同TermPositions
    TermOffsets: 單個TermOffsets存儲了該term在每個doc中的offset信息,順序同TermPositions

    2.4.5 在倒排中的查詢邏輯

    一個query進來,首先會被分詞產生過個term,然后在.tip文件中查找對應field的FST(Finite State Transducer) 根據FST查找到的信息去.tim文件中查找具體的term信息,
    在.tim中找到對應的TermStats,TermMetadata以后,可以從TermStats中拿到當前term的DF值(即出現了該term的文檔數量),從TermMetadata中拿到對應的指向.doc中的跳躍表,docIdList的指針,指向.pos .pay的指針等。
    假如多個term的關系是and,那么就會根據.doc中的skipList進行docIdList合并,或者有position的要求,還要判斷term在各個doc中的position信息是否滿足要求,然后進行打分,這個時候一般需要根據docId里面存儲的TF(term frequency term在文檔中出現的次數)
    根據打分公司進行排序即可。

    到這里lucene的倒排和正排信息基本上都梳理的差不多了。

    2.5 docvalues信息的存儲 .dvd .dvm

    doc-values信息是為了能夠根據doc-number快速的查找到該doc對應的某個屬性,在實際應用中最多的場景用于提供給搜索結果一個排序規則。
    lucene 使用.dvd .dvm存儲doc-values信息,.dvd存儲doc-values的data, .dvm存儲doc-values的元數據。

    lucene現有的doc-values種類有
    BinaryDocValues
    SortedSetDocValues
    SortedDocValues
    SortedNumericDocValues
    NumericDocValues
    實現的存儲結構不同,但是支撐的功能基本上是一致的

    可以參看
    這里
    這里

    2.6 多維數值類信息point的存儲 .dim .dii

    從Lucene6.0開始出現點數據(Point Value)的概念,通過將多維度的點數據生成KD-tree結構,來實現快速的單維度的范圍查詢(比如 IntPoint.newRangeQuery)以及N dimesional shape intersection filtering。
    索引文件.dim中的數據結構由一系列的block組成,在內存中展現為一顆滿二叉樹(單維度可能不是,這塊內容會在介紹數值類型的范圍查詢時候介紹),并且葉子節點描述了所有的點數據。
    單個維度的數值查詢一般比較容易實現,比如使用二叉搜索樹(BST),紅黑樹(RBT),B-Tree等都可以實現。
    但是多維度就沒有這么容易實現了,但是基本也是基于樹結構的基本形式來實現的。
    對于多維數據的實現的樹有

    R-Tree(R樹): R-tree
    R±Tree(R+樹): R+ tree
    R*-Tree(R樹): R_tree

    K-D-Tree(K維樹): k-d tree
    K-D-B樹(K維B樹): K-D-B-tree
    BKD-Tree
    Segment-Tree(線段樹): Segment tree
    其實這些樹具體的數據結構我也大部分都不了解,只是知道他們適用于多維空間查詢

    lucene 采用的是BKD-Tree來做多維空間查詢索引
    BKD-Tree實際上是對K-D-B樹(K維B樹)的一種優化實現,可以實現更加高效的修改操作。

    lucene的point類型有

  • IntPoint: int indexed for exact/range queries.
  • LongPoint: long indexed for exact/range queries.
  • FloatPoint: float indexed for exact/range queries.
  • DoublePoint: double indexed for exact/range queries.
  • .dim 文件存儲了每個filed的數據和索引index
    .dii 文件存儲了元信息,主要是存儲了每個filed的index在.dim文件中的地址

    待處理問題

    標準化因子包括哪些,有doc級別的,有field級別的,是如何存儲的

    Field.Index 索引選項
    Index.ANALYZED 進行分詞和索引
    Index.NOT_ANALYZED 進行索引,但是不進行分詞
    Index.ANALYZED_NOT_NORMS 進行分詞但是不存儲norms信息,這個norms信息包含了索引時間和權值等
    Index.NOT_ANALYZED_NOT_NORMS 既部分詞也不存儲norms信息
    Index.NO 不進行索引
    4.0以后Field.Index廢棄

    StringField 不分詞并索引的字段,搜索時整字段匹配
    TextField 分詞并索引
    StoredField 僅保存,不索引

    Lucene7之后,去除了Index時的boost加權操作。這里應該是指使用了BM25之后,boost的作用越來越小吧。

    總結

    以上是生活随笔為你收集整理的lucene7.5的数据结构的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。