Lucene4.2源码解析之fdt和fdx文件的读写——fdx文件存储一个个的Block,每个Block管理着一批Chunk,通过docID读取到document需要完成Segment、Block、Chunk、document四级查询,引入了LZ4算法对fdt的chunk docs进行了实时压缩/解压
通常在搜索打分完毕后,IndexSearcher会返回一个docID序列,但是仅仅有docID我们是无法看到存储在索引中的document,这时候就需要通过docID来得到完整Document信息,这个过程就需要对fdx/fdt文件进行读操作。为了更清楚地了解fdx/fdt文件的作用,本文把fdx/fdt文件的读和写整合到了一起,尽管这在Lucene中是两个分开的过程。
1. 索引生成阶段
索引生成阶段包含着一个复杂的过程,所以了解本文前最好对Lucene的索引架构有一定的了解,可以参考博客:Lucene的索引链结构_IndexChain
由于在数据处理的过程中大量用到Packed,所以对数据的压缩最好也要有一点的了解,可以参考博客:Lucene源代码学习之PackedInts;由于在存储的过程也用到了LZ4算法,关于LZ4算法的原理,可以参考博客:lucene源代码学习之LZ4压缩算法在lucene中应用
1.1 fdx/fdt文件的创建。
fdx/fdt文件的创建完整线条如下:

在dwpt完成一个document的分析时,如果CompressionStoreFieldsWriter没有实例化,则创建:

1.2 fdx/fdt文件的格式。
具体参考Lucene41StoredFieldsFormat.html (见Lucene4.2.0的docs)
fdt文件结构:

上图理解起来也不难,<Header>和PackedIntsVersion略过,我们重点关注<Chunk>,Chunk的中文意思是”大块”,我们可以理解为数据的存储区域。在内存中表现为缓存。一个Chunk由5个部分组成:DocBase表示当前的Chunk块的起始DocId;ChunkDocs表示当前Chunk中的doc个数;DocFieldCounts是一个数组,表示每个doc中Field的个数;DocLengths也是一个数组,表示每个doc占用byte的个数,即doc的长度;<CompressedDocs>即doc的内容,用LZ4算法压缩存储。FieldNumAndType是把FieldNumber和FieldType合并到一个VLong字段里面,整个<CompressedDocs>就是FieldNumAndType和Value的交替序列。
fdx文件结构:

fdx文件重点关注的是<Block>,一个Block由三个部分组成:BlockChunks表示当前Block中Chunk的个数;<DocBases>表示当前Block中每个Chunk的doc个数,可以看作一个数组;<StartPointers>表示当前Block中每个Chunk在fdt文件中的起始位置,其结构与<DocBases>相同。
尽管fdx/fdt文件只是Lucene的正向文件,并不是Lucene的核心。但是还是有干货的。在Lucene4中引入了LZ4算法对fdt的doc进行了实时压缩/解压。而且用SPI(Service Provider Interface)技术对架构进行了重构。
1.3 fdx/fdt文件的写入。
fdx/fdt文件的写入操作非常清晰。逻辑上都在CompressingStoredFieldsWriter类中完成,而CompressingStoredFieldsIndexWriter则作为其成员变量。其写入的顺序与上面的格式一致,只是有些名字不一样。在写入docs的过程中,用GrowableByteArrayDataOutput作为缓存,直到缓存满了,才flush到硬盘上去。用LZ4算法压缩就是在flush时处理的。(关于LZ4算法会在另外的博文中描述)
fdt文件的写入:
fdt文件的基本单位是Chunk,这一点需要牢记。一个Chunk写入到文件中的代码如下:<对照着前面的图看代码>

那么什么时候会调用上面的flush函数呢?
情况一:索引提交.
情况二:doc的大小或者doc的数量超过设定阈值.一般是1<<14=16384 (参见函数triggerFlush)
通过观察flush函数,我们会发现fdt文件的写入非常简单,就两句代码:

前面一句代码记录整个chunk中的docBase(最小docID),numBufferedDocs(doc数量),numStoredFields(每个doc的Field个数),lengths(每个doc的长度),一共四种信息.在记录numStoredFields和lengths时,用PackedInts及其它的方式对内容进行了压缩。后面一句代码记录整个chunk中的doc的完整内容(用LZ4算法进行压缩).
关于writeHeader(docBase,numBufferedDocs, numStoredFields, lengths); 这一句代码,存储numBufferedDocs和存储numStoredFields方式是一样的,存储方式如下:

(上图截自于Lucene41StoredFieldsFormat.html)
解释一下上图:在存储DocFieldCounts,即numBufferedDocs时,如果ChunkDocs=1(即当前Chunk只有一个doc),那么一个VInt存储就足够了;否则首先存储一个VInt的标志位,暂时称为bitsRequired。如果bitsRequired = 0 ,代表当前Chunk中所有doc中FieldCount相同;否则用Packed Array来存储DocFieldCounts,Packed Array中每个值占用的bit数即bitsRequired。
DocLengths的存储方式与DocFieldCounts相同,实现的代码如下:

fdx文件的写入
fdx是fdt文件的辅助文件.如果说fdt是一本书的正文,那么fdx就是目录.fdx的基本单位是Block,一个Block中包含多个Chunk。
一个Block写入到fdx文件中代码参看CompressingStoredFieldsIndexWriter.wirteBlock方法。由于代码太长,这里就不贴出来了。
一个Block包含三方面的内容:
1 ChunkCount;
2 <DocBases>;
3 <StartPointers> ;
如果细细读这两段代码,会发现两段代码逻辑相似性达90%。确实,这两段代码的内容的处理方式上是一样的。在LUCENE-4512这个Improvement里面,有这样一段文字能帮助我们理解上面代码:

存的是文件位置startpointer!
这段文字讲了一个技巧:“存储真实值和平均值的差值来代替存储真实值”;比如有下面几个数据需要存储到文件中:[10000,9888,10002,99997,10003];各个数与平均值之间的差值如下:[0,-2,2,-3,3] ,用差值存储就可以节约很多bits了。但是这样做又带来一个新的问题:负数的符号位都在最高位,而且PackedInts无法存储负数。因此需要对数据进行转码,转码方式就是zigzag转码。Zigzag编码的方法非常简单:
Int32: (n << 1) ^ (n >> 31)
Int64: (n << 1) ^ (n >> 63)
Zigzag编码主要在于对负数的压缩,比如-1(1111 1111 1111 1111 1111 1111 1111 1111),经过转码后,变成了1(0000 0000 0000 0000 0000 0000 0000 0001),节约了很多符号位。
经过Zigzag编码的数怎么还原呢?
(n>>> 1) ^ -(n & 1)
了解了原理,我们再来分析<DocBases>内容的写入过程:
第一步:计算平均值(avgChunkDocs)
一般最后一个chunk都没有存满,所以docNum会低于其它的Chunk,所以在计算平均值的时候不用它。
第二步:存储docBase和平均值(avgChunkDocs)
第三步:计算最大的差值(delta),这个delta是用来计算bitsRequired
第四步:用PackedInts来压缩并存储docBaseDeltas。
存储<startPointerDeltas>的逻辑与<DocBases>类似,就不再赘述了。
本文出自 “每天进步一点点” 博客,请务必保留此出处http://sbp810050504.blog.51cto.com/2799422/1533162
Lucene4.2源码解析之fdt和fdx文件的读写——fdx文件存储一个个的Block,每个Block管理着一批Chunk,通过docID读取到document需要完成Segment、Block、Chunk、document四级查询,引入了LZ4算法对fdt的chunk docs进行了实时压缩/解压的更多相关文章
- Lucene4.2源码解析之fdt和fdx文件的读写(续)——fdx文件存储一个个的Block,每个Block管理着一批Chunk,通过docID读取到document需要完成Segment、Block、Chunk、document四级查询,引入了LZ4算法对fdt的chunk docs进行了实时压缩/解压
2 索引读取阶段 当希望通过一个DocId得到Doc的全部内容,那么就需要对fdx/fdt文件进行读操作了.具体的代码在CompressingStoredFieldsReader类里面.与 ...
- elementUi源码解析(1)--项目结构篇
因为在忙其他事情好久没有更新iview的源码,也是因为后面的一些组件有点复杂在考虑用什么方式把复杂的功能逻辑简单的展示出来,还没想到方法,突然想到element的组件基本也差不多,内部功能的逻辑也差不 ...
- [源码解析] 并行分布式任务队列 Celery 之 负载均衡
[源码解析] 并行分布式任务队列 Celery 之 负载均衡 目录 [源码解析] 并行分布式任务队列 Celery 之 负载均衡 0x00 摘要 0x01 负载均衡 1.1 哪几个 queue 1.1 ...
- IdentityServer4源码解析_5_查询用户信息接口
协议简析 UserInfo接口是OAuth2.0中规定的需要认证访问的接口,可以返回认证用户的声明信息.请求UserInfo接口需要使用通行令牌.响应报文通常是json数据格式,包含了一组claim键 ...
- Python2 基本数据结构源码解析
Python2 基本数据结构源码解析 Contents 0x00. Preface 0x01. PyObject 0x01. PyIntObject 0x02. PyFloatObject 0x04. ...
- iOS网络请求-AFNetworking源码解析
趁着端午节日,自己没有什么过多的安排,准备花4-5天左右,针对网络请求源码AFNetworking和YTKNetwork进行解析以及这两年多iOS实际开发经验(其实YTKNetwork也是对AFNet ...
- [源码解析] 深度学习分布式训练框架 horovod (18) --- kubeflow tf-operator
[源码解析] 深度学习分布式训练框架 horovod (18) --- kubeflow tf-operator 目录 [源码解析] 深度学习分布式训练框架 horovod (18) --- kube ...
- 给jdk写注释系列之jdk1.6容器(4)-HashMap源码解析
前面了解了jdk容器中的两种List,回忆一下怎么从list中取值(也就是做查询),是通过index索引位置对不对,由于存入list的元素时安装插入顺序存储的,所以index索引也就是插入的次序. M ...
- Android -- 从源码解析Handle+Looper+MessageQueue机制
1,今天和大家一起从底层看看Handle的工作机制是什么样的,那么在引入之前我们先来了解Handle是用来干什么的 handler通俗一点讲就是用来在各个线程之间发送数据的处理对象.在任何线程中,只要 ...
随机推荐
- 【转载】C#中PadLeft函数按特定字符补足字符串长度
在C#开发过程中字符串String类处理过程中,有时字符串长度不够时,需要在左侧指定特定的字符来补足字符串长度,此时可以使用String类下的PadLeft方法对字符串的左边进行按特定的字符和特定的长 ...
- STM8 内部flash
举例 typedef enum { FLASH_MEMTYPE_PROG = (u8)0x00, /*!< Program memory */ FLASH_MEMTYPE_DATA = (u8) ...
- orangepi设置vnc开机自启动
首先需要创建一个文件,执行以下命令,自动打开空白文件,在里面编辑内容即可 sudo gedit /etc/init.d/tightvncserver 把以下内容粘贴进去,然后点击保存即可. #!/bi ...
- SAP Marketing Cloud功能简述(一) : Contacts和Profiles
Dong Grace是Jerry的同事,目前是SAP成都研究院数字创新空间团队的一名实习生,明年7月才毕业.这篇文章是Grace对SAP Marketing Cloud功能梳理的一篇学习笔记,因为篇幅 ...
- Linux命令——ulimit
参考:https://www.cnblogs.com/kongzhongqijing/p/5784293.html
- 笔记一下debian8升级到debian9遇到的几个坑
由于debian8不再维护了,出于安全需要,参照官方的方法,在线升级到debian9,结果遇到了好多坑,虽然没死人,但也够惨了 坑1.升级后,多占了很多空间,莫明奇妙的把 / 分区占的满满的,由于之前 ...
- centos 安装ELK
准备安装环境 由于本人的centos是通过虚拟机来进行安装的,为了本地电脑能够访问centos系统中的端口,则需要把防火墙进行关闭,通过以下方式进行关闭防火墙. # vi /etc/sysconfig ...
- 【转】通过BeanNameAutoProxyCreator改变臃肿代码
https://www.cnblogs.com/zdd-java/p/7861824.html 前言: 最近接手了一个项目,大概过了下需求,然后打开项目准备开搞的时候发现一个问题,这个项目是提供res ...
- 大数据之路week07--day04 (Linux 中查看文件内容的关键字处)
Linux如何对文件内容中的关键字进行查找 如果是用vi打开文件后,在命令行下输入“/关键字” 如果是在没有打开文件的前提就用"cat 文件名 | grep "关键字" ...
- vue 使用element ui报错解决方案
安装: npm i element-theme-default -S main.js增加 import ElementUI from 'element-ui' import 'element-ui/l ...