未完待續…
壓縮列表ziplist
1.簡介
連續,無序的數據結構。壓縮列表是 Redis 爲了節約內存而開發的, 由一系列特殊編碼的連續內存塊組成的順序型(sequential)數據結構。
2.組成
屬性 | 類型 | 長度 | 用途 |
---|---|---|---|
zlbytes | uint_32t | 4B | 記錄整個壓縮列表佔用的內存字節數:在對壓縮列表進行內存重分配, 或者計算 zlend的位置時使用 |
zltail | uint_32t | 4B | 記錄壓縮列表表尾節點距離壓縮列表的起始地址有多少字節:通過這個偏移量,程序無須遍歷整個壓縮列表就可以確定表尾節點的地址。 |
zllen | uint_16t | 2B | 記錄了壓縮列表包含的節點數量: 當這個屬性的值小於UINT16_ MAX (65535)時, 這個屬性的值就是壓縮列表包含節點的數量; 當這個值等於 UINT16_MAX 時, 節點的真實數量需要遍歷整個壓縮列表才能計算得出。 |
entryX | 列表節點 | 不定 | 壓縮列表包含的各個節點,節點的長度由節點保存的內容決定。 |
zlend | uint_8t | 1B | 特殊值 0xFF (十進制 255 ),用於標記壓縮列表的末端。 |
3.壓縮列表節點的構成
一個壓縮列表可以包含任意多個節點(entry), 每個節點可以保存一個字節數組或者一個整數值(小整數值或者長度比較短的字符串)。
(1)節點的 previous_entry_length 屬性以字節爲單位, 記錄了壓縮列表中前一個節點的長度
(1)如果前一節點的長度小於 254 字節, 那麼 previous_entry_length 屬性的長度爲 1 字節: 前一節點的長度就保存在這一個字節裏面。例如:值爲0x05
(2)如果前一節點的長度大於等於 254 字節, 那麼 previous_entry_length 屬性的長度爲 5 字節: 其中屬性的第一字節會被設置爲 0xFE (十進制值 254), 而之後的四個字節則用於保存前一節點的長度。例如:值爲0xFE00002766;0xFE表明這是一個5字節長的屬性,之後的四個字節 0x00002766(10086)纔是前一節點的實際長度。
程序可以通過指針運算, 根據當前節點的起始地址來計算出前一個節點的起始地址。
(2)節點的 encoding 屬性記錄了節點的 content 屬性所保存數據的類型以及長度:
一字節(00)、兩字節(01)或者五字節長(10), 值的最高位爲 00 、 01 或者 10 的是字節數組編碼: 這種編碼表示節點的 content 屬性保存着字節數組, 數組的長度由編碼除去最高兩位之後的其他位記錄;
一字節長, 值的最高位以 11 開頭的是整數編碼: 這種編碼表示節點的 content 屬性保存着整數值, 整數值的類型和長度由編碼除去最高兩位之後的其他位記錄;
字節數組編碼 | 編碼長度 | content屬性保存的值 |
---|---|---|
00bbbbbb | 1B | 長度小於等於63 字節的字節數組 |
01bbbbbb xxxxxxxx | 2B | 長度小於等於16 383 字節的字節數組 |
10______ aaaaaaaa bbbbbbbb cccccccc dddddddd | 5B | 長度小於等於 4 294 967 295 的字節數組 |
整數編碼 | 編碼長度 | content屬性保存的值 |
---|---|---|
11000000 | 1B | int16_t 類型的整數 |
11010000 | 1B | int32_t 類型的整數 |
11100000 | 1B | int64_t 類型的整數 |
11110000 | 1B | 24 位有符號整數 |
11111110 | 1B | 8 位有符號’些數 |
1111xxxx | 1B | 使用這一編碼的節點沒有相應的content 屬性,因爲編碼本身的xxxx 四個位已經保存了一個介於0 和12 之間的值,所以它無須content 屬性 |
(3)節點的 content 屬性負責保存節點的值, 節點值可以是一個字節數組或者整數, 值的類型和長度由節點的 encoding 屬性決定。
3.“連鎖更新”
前面說過,每個節點的previous_entry _length 屬性都記錄了前一個節點的長度:
(1)如果前一節點的長度小於254 字節,那麼previ ous_ entry_length 屬性需要用
1字節長的空間來保存這個長度值。
(2)如果前一節點的長度大於等於254 字節,那麼previous entry length 屬性需
要用5 字節長的空間來保存這個長度值。
如果我們將一個長度大於等於 254 字節的新節點 new 設置爲壓縮列表的表頭節點,那麼麻煩的事情來了,由於previous entry length大小不夠用(1->5B),後面所有的節點可能都要重新分配內存大小。因爲連鎖更新在最壞情況下需要對壓縮列表執行 N 次空間重分配操作, 而每次空間重分配的最壞複雜度爲 O(N) , 所以連鎖更新的最壞複雜度爲 O(N^2) 。
但是呢,儘管連鎖更新的複雜度較高,但它真正造成性能問題的機率是很低的。
(1)首先,壓縮列表裏要恰好有多個連續的、長度介於250 字節至253 宇節之間的節點,連鎖更新纔有可能被引發,在實際中,這種情況並不多見;
(2)其次,即使出現連鎖更新,但只要被更新的節點數量不多,就不會對性能造成任何影響:比如說,對三五個節點進行連鎖更新是絕對不會影響性能的;
因爲以上原因, ziplistPush 等命令的平均複雜度僅爲0(的,在實際中,我們可以放心地使用這些函數,而不必擔心連鎖更新會影響壓縮列表的性能。