如今咱們的背景是有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 全儲存起來了,這樣直接定位到指定的位置了。少了一個二分搜索。
定位到某個塊以後, 果真採用暴力循環來一個一個的增長偏移,而後查找對應的分詞。
找到了記錄對應的位置的四大元信息。
再而後因爲數據量已經很小了,就把匹配的數據取出來便可。固然,取數據的時候會進行布爾操做,並且會加上權值計算,這樣就搜索知足條件的前若干條了。