lucene索引结构的整理

 

    倒排索引结构,示例如下:

     

    设有 2 篇文章:

    文章1的内容为:Tom lives in Guangzhou,I live in Guangzhou too.

    文章2的内容为:He once lived in Shanghai.

     

  1. 分词:英文以空格分隔,中文根据基础词典和扩展词典分隔为字和词。
  2. 去停用词、标点符号、大小写转换等,由 Analyzer 完成。
  3. 文章1的所有关键词为:[tom] [live] [guangzhou] [i] [live] [guangzhou]

    文章2的所有关键词为:[he] [live] [shanghai]

     

    建立倒排索引:

    Term

    文章号(出现频率)

    出现位置

    guangzhou

    1[2]

    3,6

    he

    2[1]

    2

    i

    1[1]

    4

    live

    1[2],2[1]

    2,5,2

    shanghai

    2[1]

    3

    tom

    1[1]

    1

     

    lucene将上面三列分别作为词典文件(Term Dictionary)、频率文件(frequencies)、位置文件 (positions)保存。其中词典文件不仅保存有每个关键词,还保留了指向频率文件和位置文件的指针,通过指针可以找到该关键字的频率信息和位置信息。Lucene中使用了field的概念,用于表达信息所在位置(如标题中,文章中,url中),在建索引中,该field信息也记录在词典文件中,每个关键词都有一个field信息(因为每个关键字一定属于一个或多个field)。

     

    压缩:

  4. term 压缩:
  5. 关键词压缩为<前缀长度,后缀>。例如:当前词为“阿拉伯语”,上一个词为“阿拉伯”,那么“阿拉伯语”压缩为<3,语>

  6. 数字压缩:
  7. 数字只保存与上一个值的差值。例如当前文章号是16389,上一文章号是16382,压缩后保存7。

     

     

    lucene 建立索引的流程:

  8. 指定生成 index 的目录;
  9. 创建 IndexWriter 对象 :初始化 segments 文件
  10. IndexWriter writer = new IndexWriter(index_dir,new StandardAnalyzer(),false);

    第一个参数:生成 index 的目录;

    第二个参数:生成索引所用的分析器;

    第三个参数: true 为重新索引, false 为增量索引。

  11. 生成一个 document 对象
  12. Document doc = new Document();

    doc.add(new Field(name,value,Field.Store.YES,Field.Index,TOKENIZED));

    域的属性:是否 store ,是否 index ,是否切词

  13. doc 加入 IndexWriter 对象
  14.   writer.add(doc);

  15. 优化并关闭 IndexWriter 对象
  16. writer.Optimize();  // 将所有的 segments 合并为一个

    writer.close();

     

     

    lucene 索引文件结构:

    逻辑结构:

     

    每当往 index 中加入一个 doc ,都会新生成一个 segment 保存这个 doc ,通过判断合并 segment

    最后通过优化索引的命令,将所有的 segment 合并为一个 index 。一般以一个 doc 为单位往 index

    添加记录,一个 doc 有几个 field 组成,每个 field 保存不同的信息,可以指定在某个域搜索。

    每个域由很多 term 组成。

     

    物理结构:

     

  17. segments:
  18. s egments 文件的结构是:

    Format+version+counter+Count+<si0.name+si0.docCount>+<si1.name+si1.docCount>…+<sin.name+sin.docCount>

    即:索引文件格式 + index 修改次数 +segment 总数 + 给新生成的 segment 取一个随机的名字

    +<segment0 名字 +segment0 文档数 >+<segment1 名字 +segment1 文档数 >

  19. deletable:
  20. lock:
  21. index.lock 文件:

    表示有进程在向索引中加入文档,或者是从索引中删除文档。这个文件防止很多文件同时修改一个索引。

  22. segment:
    1. count :要删除的文件总数;
    2. 文件名:要删除的文件名字
    • 域集合信息( .fnm 文件):
      .fnm 文件的结构是: size+<fi0.name+bits0>+<fi1.name+bits1>+...+<fin.name+bitsn>
    • 域值存储表:
      • .fdx 文件:
    • .fdt 文件:
    • 标准化因子( .nrm 文件):
    • 项字典:
    • .tis 文件:
    • size:doc term 的总数
    • indexInterval :索引间隔,默认值为 128, 这个参数决定写入 tii 文件中的 term 的数量。
    • skipInterbal :跳跃间隔,默认值为 16
    • start :当前 term 与前一个 term 相同的前缀字符串的长度。
    • length :去除了当前 term 与前一 term 共享的前缀字符串后余下的字符串长度。
    • text :余下的字符串值。
    • fieldNumber :其所在 field 的编号。
    • docFreq :出现这个 term doc 的数量。
    • freqpointerDelta: 决定了在 .frq 文件中当前词 frq 的位置。即相对于前一个词的数据的位置偏移量(如果是 0 则表示是在文件中的第一个词条)。  
    • proxPointerDelta: 决定了在 .prx 文件中当前词 prox 的位置。即相对于前一个词的数据的位置偏移量(如果是 0 则表示是在文件中的第一个词条)。  
    • skipOffset :决定了在 .frq 文件里当前词条 SkipData 的位置。它是 TermFreq 的数据长度, SkipDelta 只在 DocFreq 不小于 SkipInterval 时才存储。
    • .tii 文件:
    • 项频数( .frq 文件) 是差值?
    • 项位置( .prx 文件)
    • 被删除文档( .del 文件)
  23. 即:该 doc field 的总数 +< 0 名字 + 0 基本信息 >+< 1 名字 + 1 基本信息 >

    基本信息 :低第一位 1 表示已经索引, 0 表示未索引。低第二位 1 表示有 term 矢量, 0 表示没有 term 矢量

    用于在域信息文件中定位一个特定文档的域信息。

    文档 n 的域信息是第 n*8 开始的 Uint64

    .fdt 文件的结构是:

    storecount+<field0.number+bits0+field0.value>+<field1.number+bits1+field1.value>+…+<fieldn.number+bitsn+fieldn.value>

    : 记录这个 doc 中需要存储的 filed 总数 +< 0 编号 + 0 属性(是否分词、是否包括 2 进制、是否压缩) + 记录 filed 的具体内容 >

    对每个 field ,都会生成一个 .f 文件,其中记录了每个 doc 在命中该域时乘的因子的大小。

    .f 的文件结构是: doc0.filed0.norm+ doc1.field0.norm+…+docn.field0.norm

    每个 doc 都会使用哈希表 postingTable 来保存该 doc 所包含的 term 的信息 这个哈希表的 key term ,值是 posting posting 是实际记录一个 term 在这个 doc 中信息的类。

    Posting 主要包含以下信息:

    term :一个 term 对象,表明这个 posting 是属于哪个 term 的;

    freq: 记录这个 term doc 中出现的频率;

    positions[]: 记录这个 term 每次出现在 doc 的哪个位置;

    offset :保存与 termvector 相关的内容

     

    这个 postingTable 写入文件

    .tis 文件的结构: FORMAT+size+indexInterval+skipInterbal+<term0.start+term0.length+term0.text+term0.FieldNumber+term0.DocFreq+term0.FreqpointerDelta+term0.proxPointerDelta+term0.skipOffset>+…+<termn.start+termn.length+termn.text+termn.FieldNumber+termn.DocFreq+termn.FreqpointerDelta+termn.proxPointerDelta+termn.skipOffset >

    即:

    假设前一个 term bit ,当前 term bike ,则 start=2 length=2 text== ke ”。

    .tis 文件的索引,记录了 .tis 文件中部分 term 及在 .tis 文件中的位置,默认为 term 总数的 1/128

    .frq 文件的结构 term0.freq+term1.freq+…+termn.freq

    .prx 文件的结构 term0.position[0]+(term0.position[1]-term0.position[0])+ (term0.position[2]-term0.position[1])+…+(term0.position[n-1]-term0.position[n-2])+….(termn.position[n-1]-termn.position[n-2])

    其中 term.position[n] 表示该 term field 中出现第 n+1 次时的偏移位置。

    要删除的文件保存在 deletable 文件中,这个文件记录以下数据:

    删除文件所采用的策略是 :当每次合并时尝试删除 deletable 中所标识的所有文件,有些文件可能正在被使用而无法删除,生成一个新的 deletable 文件取代原来的文件,记录新增的要删除的文件和保留那些没能够删除的文件。

  24. segment 合并 :每添加一个文档,都会生成一个 segment ,再合并 segment
  25. 判断是否合并的规则:

    设置一个 targetMergeDocs ,第一次合并时值为 10 ,接下来依次× 10 ,即第 2 次合并时值为 100 ,第 3 次合并时值为 1000 ,依次类推。每次判断时,读取一个 segment doc 数,这时有 3 种可能。

    1. 如果该 segment doc 数大于等于当前的 targetMergeDocs ,则对该 segment 不进行任何操作;

    2. 如小于,则把他的 doc 数和后续那些同样 doc 数小于 targetMergeDocs segment doc 数累加,直到累加所得的 doc 数大于或等于 targetMergeDocs 时,把这些参与了累加的 segment 合并成一个新的 segment

    3. 如果累加到最后一个 segment ,累加的 doc 数还不大于等于 targetMergeDocs 的话,则不进行合并。

    循环这个过程,直到再也不能合并时退出 MaybeMergeSegments()

    合并举例

    现在按照顺序,有如下几个 segment ,括号内为该 segment doc 数:

    s0(10), s1(10), s2(10), s3(10), s4(10), s5(10), s6(10), s7(10), s8(10), s9(1), s10(1), s11(1), s12(1), s13(1), s14(1), s15(1), s16(1), s17(1), s18(1), s19(1)

    第一遍合并后:

    s0(10), s1(10), s2(10), s3(10), s4(10), s5(10), s6(10), s7(10), s8(10) news9(10),s19(1)

    第二遍合并后:

    news0(100),s19(1)

     

    在完成索引最后阶段,有个 Optimize() 函数,这是为了在最后把所有的 segment 全部 合并成最后的一个 segment 文件 ,不论是否满足合并的条件。

     

你可能感兴趣的:(优化,Lucene,存储,文档,扩展,Dictionary)