如今咱们的背景是有16个已经排序的数据存在磁盘上。
因为数据量很大,咱们不能一次性所有读进来。html
咱们的目标是依次挑出最小的hit,而后交给索引引擎处理。git
sphinx 使用了 CSphHitQueue 这个数据结构。github
CSphHitQueue 你猜是什么? 队列? 恭喜你,猜错了。
CSphHitQueue 是一个最小堆。
且堆的最大个数是 iRawBlocks。数组
因为 iRawBlocks 个 hits 数组已经排序,因此咱们只须要获得 已排序的hits数组的第一个元素,就能够用堆获得最小的那个元素了。
而后咱们把最小的这个元素建索引压缩储存,删除最小元素,并取出最小元素所在 hits数组中下一个元素,扔到堆中。
这样就能够从小到大取出全部的元素,并逐个创建索引压缩储存了。数据结构
这段话看不懂的话,能够看下面的图。函数
其中建立索引压缩储存主要依靠这个函数spa
cidxHit ( tQueue.m_pData );
其中 tQueue.m_pData 的数据结构以下code
/// fat hit, which is actually stored in VLN index
struct CSphFatHit{
DWORD m_iDocID; ///< document ID
DWORD m_iGroupID; ///< documents group ID
DWORD m_iTimestamp; ///< document timestamp
DWORD m_iWordID; ///< word ID in current dictionary
DWORD m_iWordPos; ///< word position in current document
};
hit 是先按 m_iWordID 排序, 相等了再按 m_iDocID 排序, 最后才按 m_iWordPos 排序的。htm
如今咱们先不考虑上面的堆,咱们假设全部的 hit 已经在一个数组中了,且按上面的规则排序了。
如今咱们想作的是对这个 hit 数组建立索引,并压缩储存。blog
主要作了这个几件事。
第一,根据 m_iWordID 将分词分为 2014 块。
并使用 cidxPagesDir 记录块的偏移量(还记得索引文件第二个写入的数据吗)。
第二,对于每一块,咱们按分词分组,并在索引文件 spi 中储存每一个词组的信息。
具体储存的信息以下
第三,对于每一个hit,咱们存两部分信息。
上面的三部分信息都储存后,咱们就能够快速的解析出来。
假设咱们又上面的压缩的信息了。
咱们要搜索一个词时,会如何工做呢?
假设咱们已经获得这个词的 wordId 了,只须要二分一下,就能够再 O(log(1024)) 的时间内获得 wordId 在那个块内。
找到一个块内,出现一个问题,咱们不能再次二分查找来找到对应的分词列表。 由于这个 index 储存的是和上一个分词的相对偏移量,那只好所有读入内存,扫描一遍对偏移量求和,而后才能找到对应的词。
这个过程当中咱们进行了两次 IO 操做。
第一次读取块列表信息 cidxPagesDir。
第二次读取选中的那一块的全部数据。
虽然储存偏移量节省了一些磁盘储存,可是倒是用扫描整块数据为代价的。咱们原本能够直接二分整块数据的。
无论怎样,咱们在索引中找到了须要查找的那个分词的位置。
而后咱们能够在数据文件内读取对应的信息,而后获得对应记录的id了。
固然,上面这个只是个人推理,下面咱们来看看 sphinx 是怎么搜索的吧。
看 sphinx 的搜索方法,只须要看 CSphIndex_VLN 的 QueryEx 函数便可。
首先对查询的语句进行分词,而后读取索引头 m_tHeader, 读取分块信息 cidxPagesDir。
而后就对分词进行搜索了。
为了防止相同的分词重复查找,这里采用二层循环,先来判断这个分词以前是否搜索过,搜索过就记下搜索过的那个词的位置。
没搜索过,就搜索。
xxx代码略!
看了这个代码,和我想的有点出入,可是整体思路仍是同样的。
它是把全部的 cidxPagesDir 全储存起来了,这样直接定位到指定的位置了。少了一个二分搜索。
定位到某个块以后, 果真采用暴力循环来一个一个的增长偏移,而后查找对应的分词。
找到了记录对应的位置的四大元信息。
再而后因为数据量已经很小了,就把匹配的数据取出来便可。固然,取数据的时候会进行布尔操做,并且会加上权值计算,这样就搜索知足条件的前若干条了。