Lucene學習之四:Lucene的索引文件格式(1)

本文轉載自:http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623597.htmlhtml

Lucene的索引裏面存了些什麼,如何存放的,也即Lucene的索引文件格式,是讀懂Lucene源代碼的一把鑰匙。java

當咱們真正進入到Lucene源代碼之中的時候,咱們會發現:算法

  • Lucene的索引過程,就是按照全文檢索的基本過程,將倒排表寫成此文件格式的過程。
  • Lucene的搜索過程,就是按照此文件格式將索引進去的信息讀出來,而後計算每篇文檔打分(score)的過程。

本文詳細解讀了Apache Lucene - Index File Formats(http://lucene.apache.org/java/2_9_0/fileformats.html) 這篇文章。apache

 

1、基本概念

下圖就是Lucene生成的索引的一個實例:數據結構

image

Lucene的索引結構是有層次結構的,主要分如下幾個層次:性能

  • 索引(Index):
    • 在Lucene中一個索引是放在一個文件夾中的。
    • 如上圖,同一文件夾中的全部的文件構成一個Lucene索引。
  • 段(Segment):
    • 一個索引能夠包含多個段,段與段之間是獨立的,添加新文檔能夠生成新的段,不一樣的段能夠合併。
    • 如上圖,具備相同前綴文件的屬同一個段,圖中共兩個段 "_0" 和 "_1"。
    • segments.gen和segments_5是段的元數據文件,也即它們保存了段的屬性信息。
  • 文檔(Document):
    • 文檔是咱們建索引的基本單位,不一樣的文檔是保存在不一樣的段中的,一個段能夠包含多篇文檔。
    • 新添加的文檔是單獨保存在一個新生成的段中,隨着段的合併,不一樣的文檔合併到同一個段中。
  • 域(Field):
    • 一篇文檔包含不一樣類型的信息,能夠分開索引,好比標題,時間,正文,做者等,均可以保存在不一樣的域裏。
    • 不一樣域的索引方式能夠不一樣,在真正解析域的存儲的時候,咱們會詳細解讀。
  • 詞(Term):
    • 詞是索引的最小單位,是通過詞法分析和語言處理後的字符串。

 

Lucene的索引結構中,即保存了正向信息,也保存了反向信息。編碼

所謂正向信息:spa

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

所謂反向信息:指針

  • 保存了詞典到倒排表的映射:詞(Term) –> 文檔(Document)
  • 如上圖,包含反向信息的文件有:
    • XXX.tis,XXX.tii保存了詞典(Term Dictionary),也即此段包含的全部的詞按字典順序的排序。
    • XXX.frq保存了倒排表,也即包含每一個詞的文檔ID列表。
    • XXX.prx保存了倒排表中每一個詞在包含此詞的文檔中的位置。

在瞭解Lucene索引的詳細結構以前,先看看Lucene索引中的基本數據類型。orm

 

2、基本類型

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。

clip_image002[1]

  • Chars:是UTF-8編碼的一系列Byte。
  • String:一個字符串首先是一個VInt來表示此字符串包含的字符的個數,接着即是UTF-8編碼的字符序列Chars。

 

3、基本規則

Lucene爲了使的信息的存儲佔用的空間更小,訪問速度更快,採起了一些特殊的技巧,然而在看Lucene文件格式的時候,這些技巧卻容易使咱們感到困惑,因此有必要把這些特殊的技巧規則提取出來介紹一下。

在下不才,胡亂給這些規則起了一些名字,是爲了方便後面應用這些規則的時候可以簡單,不妥之處請你們諒解。

1. 前綴後綴規則(Prefix+Suffix)

Lucene在反向索引中,要保存詞典(Term Dictionary)的信息,全部的詞(Term)在詞典中是按照字典順序進行排列的,然而詞典中包含了文檔中的幾乎全部的詞,而且有的詞仍是很是的長的,這樣索引文件會很是的大,所謂前綴後綴規則,即當某個詞和前一個詞有共同的前綴的時候,後面的詞僅僅保存前綴在詞中的偏移(offset),以及除前綴之外的字符串(稱爲後綴)。

prefixsuffix

好比要存儲以下詞:term,termagancy,termagant,terminal,

若是按照正常方式來存儲,須要的空間以下:

[VInt = 4] [t][e][r][m],[VInt = 10][t][e][r][m][a][g][a][n][c][y],[VInt = 9][t][e][r][m][a][g][a][n][t],[VInt = 8][t][e][r][m][i][n][a][l]

共須要35個Byte.

若是應用前綴後綴規則,須要的空間以下:

[VInt = 4] [t][e][r][m],[VInt = 4 (offset)][VInt = 6][a][g][a][n][c][y],[VInt = 8 (offset)][VInt = 1][t],[VInt = 4(offset)][VInt = 4][i][n][a][l]

共須要22個Byte。(原文勘誤:此處是18個Byte)

大大縮小了存儲空間,尤爲是在按字典順序排序的狀況下,前綴的重合率大大提升。

2. 差值規則(Delta)

在Lucene的反向索引中,須要保存不少整型數字的信息,好比文檔ID號,好比詞(Term)在文檔中的位置等等。

由上面介紹,咱們知道,整型數字是以VInt的格式存儲的。隨着數值的增大,每一個數字佔用的Byte的個數也逐漸的增多。所謂差值規則(Delta)就是前後保存兩個整數的時候,後面的整數僅僅保存和前面整數的差便可。

delta

好比要存儲以下整數:16386,16387,16388,16389

若是按照正常方式來存儲,須要的空間以下:

[(1) 000, 0010][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0011][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0100][(1) 000, 0000][(0) 000, 0001],[(1) 000, 0101][(1) 000, 0000][(0) 000, 0001]

供需12個Byte。

若是應用差值規則來存儲,須要的空間以下:

[(1) 000, 0010][(1) 000, 0000][(0) 000, 0001],[(0) 000, 0001],[(0) 000, 0001],[(0) 000, 0001]

共需6個Byte。

大大縮小了存儲空間,並且不管是文檔ID,仍是詞在文檔中的位置,都是按從小到大的順序,逐漸增大的。

3. 或然跟隨規則(A, B?)

Lucene的索引結構中存在這樣的狀況,某個值A後面可能存在某個值B,也可能不存在,須要一個標誌來表示後面是否跟隨着B。

通常的狀況下,在A後面放置一個Byte,爲0則後面不存在B,爲1則後面存在B,或者0則後面存在B,1則後面不存在B。

但這樣要浪費一個Byte的空間,其實一個Bit就能夠了。

在Lucene中,採起如下的方式:A的值左移一位,空出最後一位,做爲標誌位,來表示後面是否跟隨B,因此在這種狀況下,A/2是真正的A原來的值。

ab

若是去讀Apache Lucene - Index File Formats這篇文章,會發現不少符合這種規則的:

  • .frq文件中的DocDelta[, Freq?],DocSkip,PayloadLength?
  • .prx文件中的PositionDelta,Payload? (但不徹底是,以下表分析)

固然還有一些帶?的但不屬於此規則的:

  • .frq文件中的SkipChildLevelPointer?,是多層跳躍表中,指向下一層表的指針,固然若是是最後一層,此值就不存在,也不須要標誌。
  • .tvf文件中的Positions?, Offsets?。
    • 在此類狀況下,帶?的值是否存在,並不取決於前面的值的最後一位。
    • 而是取決於Lucene的某項配置,固然這些配置也是保存在Lucene索引文件中的。
    • 如Position和Offset是否存儲,取決於.fnm文件中對於每一個域的配置(TermVector.WITH_POSITIONS和TermVector.WITH_OFFSETS)

爲何會存在以上兩種狀況,實際上是能夠理解的:

  • 對於符合或然跟隨規則的,是由於對於每個A,B是否存在都不相同,當這種狀況大量存在的時候,從一個Byte到一個Bit如此8倍的空間節約仍是很值得的。
  • 對於不符合或然跟隨規則的,是由於某個值的是否存在的配置對於整個域(Field)甚至整個索引都是有效的,而非每次的狀況都不相同,於是能夠統一存放一個標誌。
文章中對以下格式的描述使人困惑:

Positions --> <PositionDelta,Payload?> Freq

Payload --> <PayloadLength?,PayloadData>

PositionDelta和Payload是否適用或然跟隨規則呢?如何標識PayloadLength是否存在呢?

其實PositionDelta和Payload並不符合或然跟隨規則,Payload是否存在,是由.fnm文件中對於每一個域的配置中有關Payload的配置決定的(FieldOption.STORES_PAYLOADS) 。

當Payload不存在時,PayloadDelta自己不聽從或然跟隨原則。

當Payload存在時,格式應該變成以下:Positions --> <PositionDelta,PayloadLength?,PayloadData> Freq

從而PositionDelta和PayloadLength一塊兒適用或然跟隨規則。

 

4. 跳躍表規則(Skip list) 

爲了提升查找的性能,Lucene在不少地方採起的跳躍表的數據結構。

跳躍表(Skip List)是如圖的一種數據結構,有如下幾個基本特徵:

  • 元素是按順序排列的,在Lucene中,或是按字典順序排列,或是按從小到大順序排列。
  • 跳躍是有間隔的(Interval),也即每次跳躍的元素數,間隔是事先配置好的,如圖跳躍表的間隔爲3。
  • 跳躍表是由層次的(level),每一層的每隔指定間隔的元素構成上一層,如圖跳躍表共有2層。

skiplist

須要注意一點的是,在不少數據結構或算法書中都會有跳躍表的描述,原理都是大體相同的,可是定義稍有差異:

  • 對間隔(Interval)的定義: 如圖中,有的認爲間隔爲2,即兩個上層元素之間的元素數,不包括兩個上層元素;有的認爲是3,即兩個上層元素之間的差,包括後面上層元素,不包括前面的上層元素;有的認爲是4,即除兩個上層元素之間的元素外,既包括前面,也包括後面的上層元素。Lucene是採起的第二種定義。
  • 對層次(Level)的定義:如圖中,有的認爲應該包括原鏈表層,並從1開始計數,則總層次爲3,爲1,2,3層;有的認爲應該包括原鏈表層,並從0計數,爲0,1,2層;有的認爲不該該包括原鏈表層,且從1開始計數,則爲1,2層;有的認爲不該該包括鏈表層,且從0開始計數,則爲0,1層。Lucene採起的是最後一種定義。

跳躍表比順序查找,大大提升了查找速度,如查找元素72,原來要訪問2,3,7,12,23,37,39,44,50,72總共10個元素,應用跳躍表後,只要首先訪問第1層的50,發現72大於50,而第1層無下一個節點,而後訪問第2層的94,發現94大於72,而後訪問原鏈表的72,找到元素,共須要訪問3個元素便可。

然而Lucene在具體實現上,與理論又有所不一樣,在具體的格式中,會詳細說明。

相關文章
相關標籤/搜索