baiyan
所有視頻:https://segmentfault.com/a/11...redis
乍一看標題,咱們可能還不知道ziplist是何許人也。可是若是我說list、hash、zset這幾種數據結構,你們就很熟悉了。而ziplist就是這幾種數據結構的底層實現之一:算法
- list:3.2.x以前爲(ziplist + linkedlist)以後爲quicklist
- hash:數據量小的時候使用ziplist,量大時使用hashtable(dict)
- zset:數據量小的時候使用ziplist,量大時使用skiplist
咱們能夠看到,ziplist老是在一種列表、哈希、有序集合這幾種結構在存儲的數據量小的時會使用。隨着數據量的增加,會轉換到相對應較複雜的類型。咱們能夠猜想,ziplist是一種輕巧、簡單、且佔用內存小的數據結構。它可以解決在redis數據量小時的存儲問題。segmentfault
在redis的設計思想中,大多數狀況下,都是以時間換空間。因爲redis基於內存,且內存資源是至關寶貴的,因此節省空間的「性價比」相對於節省時間,顯然更高一些。在學習數據結構與算法的過程當中,咱們經常將數組和鏈表放在一塊兒比較。因爲數組使用一塊連續的內存,而鏈表分爲指針域和數據域,數組在空間利用率上明顯要高於鏈表。參考以上設計思想,若是讓咱們本身去設計ziplist的結構,咱們如何思考呢?數組
- 須要一塊連續的內存空間去存儲真正的數據
- 須要一些額外的信息字段去記錄它的長度、結束標誌、還有數據的總量等輔助信息
在ziplist中,是按照以下結構進行存儲的,是否符合你的預期呢?
每一個字段的含義以下:數據結構
- zlbytes:4個字節。記錄壓縮列表總共佔用的字節數,在對壓縮列表進行內存重分配時使用
- zltail:4個字節。可經過這個字段快速定位到鏈表末尾
- zllen:2個字節。記錄總共有多少個entry
- entry:具體數據的內容就存在這裏
- zlend:1個字節。爲十六進制值0xFF,標記一個壓縮列表的末尾
具體的數據存放在entry中。在ziplist中,能夠存儲兩種數據:學習
- 字符串(字節數組)
- 整數
舉一個例子,一個zset在數據量少的狀況下,會將元素名和分值按從小到大的順序在ziplist的entry中連續存儲:
那麼問題來了,咱們在讀取數據的時候,咱們怎麼知道是應該按照讀取字符串仍是整數類型的方式去讀取呢?咱們須要知道當前entry存儲數據的類型,即一個encoding字段,用來標識當前entry數據的類型。
除此以外,咱們在查找一個元素的時候,須要對其進行遍歷,在ziplist中是如何遍歷的呢?回想咱們在數組中的遍歷方式:ui
普通數組的遍歷是根據數組裏存儲的 數據類型來找到下一個元素的,例如int類型的數組(也是指針)訪問下一個元素時每次只須要加上相應類型的指針偏移量便可(若是用下標法表示數組,p[0]到p[1]就等效於p+1*sizeof(int)這個過程;若是用指針法,能夠用p+1來表示,它也等效於p+1*sizeof(int))
那麼在ziplist中,咱們不知道數據類型,也不知道這個數據的長度,該如何將遍歷用的指針日後挪呢?這就須要一個字段去完成這個任務,這裏就是previous_entry_length,它記錄前一個entry的長度,能夠利用它完成壓縮列表的遍歷
最後,就是最重要的字段,即存儲真正數據的字段content
以咱們上圖的例子,繼續咱們畫出entry的結構:編碼
- previous_entry_length:記錄了壓縮列表中前一個entry的長度。佔用1或5字節
- encoding:表示當前entry存儲數據的類型與數據的長度。佔用一、2或5字節
- content:真正存儲數據的地方
遍歷是查找操做的基礎,學習任意一種數據結構,遍歷都是關鍵。spa
正向遍歷ziplist:首先指針p在第一個entry的起始位置,即previous_entry_length字段的位置。因爲previous_entry_length可能佔用1個字節、也可能佔用5個字節,因此咱們須要知道如何區分這個字段佔用了1仍是5字節。表示方法以下:設計
- 若是前一個entry的長度小於254字節時,previous_entry_length用1字節表示
- 若是前一個entry的長度大於等於254字節時,previous_entry_length用5字節表示。注意此時第一個字節是固定的標誌0xFE(254),後面4個字節用來表示前一個entry的長度
因爲previous_entry_length字段的存在,咱們首先取出外部zltail字段,也就是指向ziplist結構末尾的指針,而後一次又一次地將指針減去entry中previous_entry_length字段的值,就可以將指針偏移到ziplist的頭部,原理很簡單,相信你們都可以理解,再也不贅述。因此咱們可以發現,ziplist更適合從後往前遍歷。
- zset中的對象保存的元素數量不超過128個
- zset中保存的全部元素成員的長度小於64字節
至於ziplist的關鍵之處就講完了。至於其增刪改查的具體源碼,有興趣的讀者能夠去ziplist.c中深刻查看,筆者在這篇文章裏再複製粘貼一次意義也不大。學習的過程當中,我閱讀了大量的資料,可是內容質量良莠不齊。這裏我想說,咱們在學習一種新知識的時候,不只僅要知道它是什麼樣子,也要知道它爲何是這樣的、爲何這麼作而不採用其它的替代方案?它的比較優點在哪裏?而不要簡單的堆砌概念。在學習的同時,若是沒有通過本身的思考,收效甚微。