天天看點

redis 壓縮清單壓縮清單

壓縮清單

Ziplist 是由一系列特殊編碼的記憶體塊構成的清單,一個 ziplist 可以包含多個節點(entry),每個節點可以儲存一個長度受限的字元數組(不以 \0 結尾的 char 數組)或者整數,包括:

  • 字元數組
    • 長度小于等于 63 ()位元組的字元數組
    • 長度小于等于 16383 () 位元組的字元數組
    • 長度小于等于 4294967295 ()位元組的字元數組
  • 整數
    • 4 位長,介于 0 至 12 之間的無符号整數
    • 1 位元組長,有符号整數
    • 3 位元組長,有符号整數
    • int16_t 類型整數
    • int32_t 類型整數
    • int64_t 類型整數

因為 ziplist 節約記憶體的性質,哈希鍵、清單鍵和有序集合鍵初始化的底層實作皆采用 ziplist,更多資訊請參考《哈希表》、《清單》和《有序集》章節。

本章先介紹 ziplist 的組成結構,以及 ziplist 節點的編碼方式。再介紹 ziplist 的添加操作和删除操作的執行過程,以及這兩種操作可能引起的連鎖更新現象。最後介紹 ziplist 的周遊方法和節點查找方式。

ziplist 的構成

下圖展示了一個 ziplist 的典型分布結構:

area        |<---- ziplist header ---->|<----------- entries ------------->|<-end->|

size          4 bytes  4 bytes  2 bytes    ?        ?        ?        ?     1 byte
            +---------+--------+-------+--------+--------+--------+--------+-------+
component   | zlbytes | zltail | zllen | entry1 | entry2 |  ...   | entryN | zlend |
            +---------+--------+-------+--------+--------+--------+--------+-------+
                                       ^                          ^        ^
address                                |                          |        |
                                ZIPLIST_ENTRY_HEAD                |   ZIPLIST_ENTRY_END
                                                                  |
                                                         ZIPLIST_ENTRY_TAIL
      

圖中各個域的作用如下:

長度/類型 域的值
zlbytes uint32_t 整個 ziplist 占用的記憶體位元組數,對 ziplist 進行記憶體重配置設定,或者計算末端時使用。
zltail uint32_t 到達 ziplist 表尾節點的偏移量。通過這個偏移量,可以在不周遊整個 ziplist 的前提下,彈出表尾節點。
zllen uint16_t ziplist 中節點的數量。當這個值小于 UINT16_MAX (65535)時,這個值就是 ziplist 中節點的數量;當這個值等于 UINT16_MAX 時,節點的數量需要周遊整個 ziplist 才能計算得出。
entryX ? ziplist 所儲存的節點,各個節點的長度根據内容而定。
zlend uint8_t 255 的二進制值 1111 1111 (UINT8_MAX) ,用于标記 ziplist 的末端。

為了友善地取出 ziplist 的各個域以及一些指針位址, ziplist 子產品定義了以下宏:

作用 算法複雜度
ZIPLIST_BYTES(ziplist) 取出 zlbytes 的值
ZIPLIST_TAIL_OFFSET(ziplist) 取出 zltail 的值
ZIPLIST_LENGTH(ziplist) 取出 zllen 的值
ZIPLIST_HEADER_SIZE 傳回 ziplist header 部分的長度,總是固定的 10 位元組
ZIPLIST_ENTRY_HEAD(ziplist) 傳回到達 ziplist 第一個節點(表頭)的位址
ZIPLIST_ENTRY_TAIL(ziplist) 傳回到達 ziplist 最後一個節點(表尾)的位址
ZIPLIST_ENTRY_END(ziplist) 傳回 ziplist 的末端,也即是 zlend 之前的位址

因為 ziplist header 部分的長度總是固定的(4 位元組 + 4 位元組 + 2 位元組),是以将指針移動到表頭節點的複雜度為常數時間;除此之外,因為表尾節點的位址可以通過 zltail 計算得出,是以将指針移動到表尾節點的複雜度也為常數時間。

以下是用于操作 ziplist 的函數:

函數名 作用 算法複雜度
ziplistNew 建立一個新的 ziplist
ziplistResize 重新調整 ziplist 的記憶體大小
ziplistPush 将一個包含給定值的新節點推入 ziplist 的表頭或者表尾
zipEntry 取出給定位址上的節點,并将它的屬性儲存到 zlentry 結構然後傳回
ziplistInsert 将一個包含給定值的新節點插入到給定位址
ziplistDelete 删除給定位址上的節點
ziplistDeleteRange 在給定索引上,連續進行多次删除
ziplistFind 在 ziplist 中查找并傳回包含給定值的節點
ziplistLen 傳回 ziplist 儲存的節點數量
ziplistBlobLen 以位元組為機關,傳回 ziplist 占用的記憶體大小

因為 ziplist 由連續的記憶體塊構成,在最壞情況下,當 ziplistPush 、 ziplistDelete 這類對節點進行增加或删除的函數之後,程式需要執行一種稱為連鎖更新的動作來維持 ziplist 結構本身的性質,是以這些函數的最壞複雜度都為 。不過,因為這種最壞情況出現的機率并不高,是以大可以放心使用 ziplist ,而不必太擔心出現最壞情況。

節點的構成

一個 ziplist 可以包含多個節點,每個節點可以劃分為以下幾個部分:

area        |<------------------- entry -------------------->|

            +------------------+----------+--------+---------+
component   | pre_entry_length | encoding | length | content |
            +------------------+----------+--------+---------+
      

以下幾個小節将分别對這個四個部分進行介紹。

pre_entry_length

pre_entry_length 記錄了前一個節點的長度,通過這個值,可以進行指針計算,進而跳轉到上一個節點。

area        |<---- previous entry --->|<--------------- current entry ---------------->|

size          5 bytes                   1 byte             ?          ?        ?
            +-------------------------+-----------------------------+--------+---------+
component   | ...                     | pre_entry_length | encoding | length | content |
            |                         |                  |          |        |         |
value       |                         | 0000 0101        |    ?     |   ?    |    ?    |
            +-------------------------+-----------------------------+--------+---------+
            ^                         ^
address     |                         |
            p = e - 5                 e
      

上圖展示了如何通過一個節點向前跳轉到另一個節點:用指向目前節點的指針 e ,減去 pre_entry_length 的值(0000 0101 的十進制值, 5),得出的結果就是指向前一個節點的位址 p 。

根據編碼方式的不同, pre_entry_length 域可能占用 1 位元組或者 5 位元組:

  • 1 位元組:如果前一節點的長度小于 254 位元組,便使用一個位元組儲存它的值。
  • 5 位元組:如果前一節點的長度大于等于 254 位元組,那麼将第 1 個位元組的值設為 254 ,然後用接下來的 4 個位元組儲存實際長度。

作為例子,以下是個長度為 1 位元組的 pre_entry_length 域,域的值為 128 (二進制為 1000 0000 ):

area        |<------------------- entry -------------------->|

size          1 byte             ?          ?        ?
            +------------------+----------+--------+---------+
component   | pre_entry_length | encoding | length | content |
            |                  |          |        |         |
value       | 1000 0000        |          |        |         |
            +------------------+----------+--------+---------+
      

而以下則是個長度為 5 位元組的 pre_entry_length 域,域的第一個位元組被設為 254 的二進制 1111 1110 ,而之後的四個位元組則被設定為 10086 的二進制 10 0111 0110 0110 (多餘的高位用 0 補完):

area        |<------------------------------ entry ---------------------------------->|

size          5 bytes                                     ?          ?        ?
            +-------------------------------------------+----------+--------+---------+
component   | pre_entry_length                          | encoding | length | content |
            |                                           |          |        |         |
            | 11111110 00000000000000000010011101100110 | ?        | ?      | ?       |
            +-------------------------------------------+----------+--------+---------+
            |<------->|<------------------------------->|
              1 byte       4 bytes
      

encoding 和 length

encoding 和 length 兩部分一起決定了 content 部分所儲存的資料的類型(以及長度)。

其中, encoding 域的長度為兩個 bit ,它的值可以是 00 、 01 、 10 和 11 :

  • 00 、 01 和 10 表示 content 部分儲存着字元數組。
  • 11 表示 content 部分儲存着整數。

以 00 、 01 和 10 開頭的字元數組的編碼方式如下:

編碼 編碼長度 content 部分儲存的值
00bbbbbb 1 byte 長度小于等于 63 位元組的字元數組。
01bbbbbb xxxxxxxx 2 byte 長度小于等于 16383 位元組的字元數組。
10____ aaaaaaaa bbbbbbbb cccccccc dddddddd 5 byte 長度小于等于 4294967295 的字元數組。

表格中的下劃線 _ 表示留白,而變量 b 、 x 等則代表實際的二進制資料。為了友善閱讀,多個位元組之間用空格隔開。

11 開頭的整數編碼如下:

編碼 編碼長度 content 部分儲存的值
11000000 1 byte int16_t 類型的整數
11010000 1 byte int32_t 類型的整數
11100000 1 byte int64_t 類型的整數
11110000 1 byte 24 bit 有符号整數
11111110 1 byte 8 bit 有符号整數
1111xxxx 1 byte 4 bit 無符号整數,介于 0 至 12 之間

content

content 部分儲存着節點的内容,類型和長度由 encoding 和 length 決定。

以下是一個儲存着字元數組 hello world 的節點的例子:

area      |<---------------------- entry ----------------------->|

size        ?                  2 bit      6 bit    11 byte
          +------------------+----------+--------+---------------+
component | pre_entry_length | encoding | length | content       |
          |                  |          |        |               |
value     | ?                |    00    | 001011 | hello world   |
          +------------------+----------+--------+---------------+
      

encoding 域的值 00 表示節點儲存着一個長度小于等于 63 位元組的字元數組, length 域給出了這個字元數組的準确長度 —— 11 位元組(的二進制 001011), content 則儲存着字元數組值 hello world 本身(為了友善表示, content 部分使用字元而不是二進制表示)。

以下是另一個節點,它儲存着整數 10086 :

area      |<---------------------- entry ----------------------->|

size        ?                  2 bit      6 bit    2 bytes
          +------------------+----------+--------+---------------+
component | pre_entry_length | encoding | length | content       |
          |                  |          |        |               |
value     | ?                |    11    | 000000 | 10086         |
          +------------------+----------+--------+---------------+
      

encoding 域的值 11 表示節點儲存的是一個整數;而 length 域的值 000000 表示這個節點的值的類型為 int16_t ;最後, content 儲存着整數值 10086 本身(為了友善表示, content 部分用十進制而不是二進制表示)。

建立新 ziplist

函數 ziplistNew 用于建立一個新的空白 ziplist ,這個 ziplist 可以表示為下圖:

area        |<---- ziplist header ---->|<-- end -->|

size          4 bytes   4 bytes 2 bytes  1 byte
            +---------+--------+-------+-----------+
component   | zlbytes | zltail | zllen | zlend     |
            |         |        |       |           |
value       |  1011   |  1010  |   0   | 1111 1111 |
            +---------+--------+-------+-----------+
                                       ^
                                       |
                               ZIPLIST_ENTRY_HEAD
                                       &
address                        ZIPLIST_ENTRY_TAIL
                                       &
                               ZIPLIST_ENTRY_END
      

空白 ziplist 的表頭、表尾和末端處于同一位址。

建立了 ziplist 之後,就可以往裡面添加新節點了,根據新節點添加位置的不同,這個工作可以分為兩類來進行:

  1. 将節點添加到 ziplist 末端:在這種情況下,新節點的後面沒有任何節點。
  2. 将節點添加到某個/某些節點的前面:在這種情況下,新節點的後面有至少一個節點。

以下兩個小節分别讨論這兩種情況。

将節點添加到末端

将新節點添加到 ziplist 的末端需要執行以下三個步驟:

  1. 記錄到達 ziplist 末端所需的偏移量(因為之後的記憶體重配置設定可能會改變 ziplist 的位址,是以記錄偏移量而不是儲存指針)
  2. 根據新節點要儲存的值,計算出編碼這個值所需的空間大小,以及編碼它前一個節點的長度所需的空間大小,然後對 ziplist 進行記憶體重配置設定。
  3. 設定新節點的各項屬性: pre_entry_length 、 encoding 、 length 和 content 。
  4. 更新 ziplist 的各項屬性,比如記錄空間占用的 zlbytes ,到達表尾節點的偏移量 zltail ,以及記錄節點數量的 zllen 。

舉個例子,假設現在要将一個新節點添加到隻含有一個節點的 ziplist 上,程式首先要執行步驟 1 ,定位 ziplist 的末端:

area        |<---- ziplist header ---->|<--- entries -->|<-- end -->|

size          4 bytes  4 bytes  2 bytes  5 bytes          1 bytes
            +---------+--------+-------+----------------+-----------+
component   | zlbytes | zltail | zllen | entry 1        | zlend     |
            |         |        |       |                |           |
value       |  10000  |  1010  |   1   | ?              | 1111 1111 |
            +---------+--------+-------+----------------+-----------+
                                       ^                ^
                                       |                |
address                         ZIPLIST_ENTRY_HEAD   ZIPLIST_ENTRY_END
                                       &
                                ZIPLIST_ENTRY_TAIL
      

然後執行步驟 2 ,程式需要計算新節點所需的空間:

假設我們要添加到節點裡的值為字元數組 hello world ,那麼儲存這個值共需要 12 位元組的空間:

  • 11 位元組用于儲存字元數組本身;
  • 另外 1 位元組中的 2 bit 用于儲存類型編碼 00 , 而其餘 6 bit 則儲存字元數組長度 11 的二進制 001011 。

另外,節點還需要 1 位元組,用于儲存前一個節點的長度 5 (二進制 101 )。

合算起來,為了添加新節點, ziplist 總共需要多配置設定 13 位元組空間。以下是配置設定完成之後, ziplist 的樣子:

area        |<---- ziplist header ---->|<------------ entries ------------>|<-- end -->|

size          4 bytes  4 bytes  2 bytes  5 bytes          13 bytes           1 bytes
            +---------+--------+-------+----------------+------------------+-----------+
component   | zlbytes | zltail | zllen | entry 1        | entry 2          | zlend     |
            |         |        |       |                |                  |           |
value       |  10000  |  1010  |   1   | ?              | pre_entry_length | 1111 1111 |
            |         |        |       |                | ?                |           |
            |         |        |       |                |                  |           |
            |         |        |       |                | encoding         |           |
            |         |        |       |                | ?                |           |
            |         |        |       |                |                  |           |
            |         |        |       |                | length           |           |
            |         |        |       |                | ?                |           |
            |         |        |       |                |                  |           |
            |         |        |       |                | content          |           |
            |         |        |       |                | ?                |           |
            |         |        |       |                |                  |           |
            +---------+--------+-------+----------------+------------------+-----------+
                                       ^                ^
                                       |                |
address                       ZIPLIST_ENTRY_HEAD   ZIPLIST_ENTRY_END
                                       &
                              ZIPLIST_ENTRY_TAIL
      

步驟三,更新新節點的各項屬性(為了友善表示, content 的内容使用字元而不是二進制來表示):

area        |<---- ziplist header ---->|<------------ entries ------------>|<-- end -->|

size          4 bytes  4 bytes  2 bytes  5 bytes          13 bytes           1 bytes
            +---------+--------+-------+----------------+------------------+-----------+
component   | zlbytes | zltail | zllen | entry 1        | entry 2          | zlend     |
            |         |        |       |                |                  |           |
value       |  10000  |  1010  |   1   | ?              | pre_entry_length | 1111 1111 |
            |         |        |       |                | 101              |           |
            |         |        |       |                |                  |           |
            |         |        |       |                | encoding         |           |
            |         |        |       |                | 00               |           |
            |         |        |       |                |                  |           |
            |         |        |       |                | length           |           |
            |         |        |       |                | 001011           |           |
            |         |        |       |                |                  |           |
            |         |        |       |                | content          |           |
            |         |        |       |                | hello world      |           |
            |         |        |       |                |                  |           |
            +---------+--------+-------+----------------+------------------+-----------+
                                       ^                ^
                                       |                |
address                       ZIPLIST_ENTRY_HEAD   ZIPLIST_ENTRY_END
                                       &
                              ZIPLIST_ENTRY_TAIL
      

最後一步,更新 ziplist 的 zlbytes 、 zltail 和 zllen 屬性:

area        |<---- ziplist header ---->|<------------ entries ------------>|<-- end -->|

size          4 bytes  4 bytes  2 bytes  5 bytes          13 bytes           1 bytes
            +---------+--------+-------+----------------+------------------+-----------+
component   | zlbytes | zltail | zllen | entry 1        | entry 2          | zlend     |
            |         |        |       |                |                  |           |
value       |  11101  |  1111  |  10   | ?              | pre_entry_length | 1111 1111 |
            |         |        |       |                | 101              |           |
            |         |        |       |                |                  |           |
            |         |        |       |                | encoding         |           |
            |         |        |       |                | 00               |           |
            |         |        |       |                |                  |           |
            |         |        |       |                | length           |           |
            |         |        |       |                | 001011           |           |
            |         |        |       |                |                  |           |
            |         |        |       |                | content          |           |
            |         |        |       |                | hello world      |           |
            |         |        |       |                |                  |           |
            +---------+--------+-------+----------------+------------------+-----------+
                                       ^                ^                  ^
                                       |                |                  |
address                                |          ZIPLIST_ENTRY_TAIL   ZIPLIST_ENTRY_END
                                       |
                               ZIPLIST_ENTRY_HEAD
      

到這一步,添加新節點到表尾的工作正式完成。

注解

這裡沒有示範往空 ziplist 添加第一個節點的過程,因為這個過程和上面示範的添加第二個節點的過程類似;而且因為第一個節點的存在,添加第二個節點的過程可以更好地展示“将節點添加到表尾”這一操作的一般性。

将節點添加到某個/某些節點的前面

比起将新節點添加到 ziplist 的末端,将一個新節點添加到某個/某些節點的前面要複雜得多,因為這種操作除了将新節點添加到 ziplist 以外,還可能引起後續一系列節點的改變。

舉個例子,假設我們要将一個新節點 new 添加到節點 prev 和 next 之間:

add new entry here
           |
           V
+----------+----------+----------+----------+----------+
|          |          |          |          |          |
|   prev   |   next   | next + 1 | next + 2 |   ...    |
|          |          |          |          |          |
+----------+----------+----------+----------+----------+
      

程式首先為新節點擴大 ziplist 的空間:

+----------+----------+----------+----------+----------+----------+
|          |          |          |          |          |          |
|   prev   |   ???    |   next   | next + 1 | next + 2 |   ...    |
|          |          |          |          |          |          |
+----------+----------+----------+----------+----------+----------+

           |<-------->|
              expand
              space
      

然後設定 new 節點的各項值 —— 到目前為止,一切都和前面介紹的添加操作一樣:

set value,
             property,
             length,
             etc.
                |
                v
+----------+----------+----------+----------+----------+----------+
|          |          |          |          |          |          |
|   prev   |   new    |   next   | next + 1 | next + 2 |   ...    |
|          |          |          |          |          |          |
+----------+----------+----------+----------+----------+----------+
      

現在,新的 new 節點取代原來的 prev 節點,成為了 next 節點的新前驅節點,不過,因為這時 next 節點的 pre_entry_length 域編碼的仍然是 prev 節點的長度,是以程式需要将 new 節點的長度編碼進 next 節點的 pre_entry_length 域裡,這裡會出現三種可能:

  1. next 的 pre_entry_length 域的長度正好能夠編碼 new 的長度(都是 1 位元組或者都是 5 位元組)
  2. next 的 pre_entry_length 隻有 1 位元組長,但編碼 new 的長度需要 5 位元組
  3. next 的 pre_entry_length 有 5 位元組長,但編碼 new 的長度隻需要 1 位元組

對于情況 1 和 3 ,程式直接更新 next 的 pre_entry_length 域。

如果是第二種情況,那麼程式必須對 ziplist 進行記憶體重配置設定,進而擴充 next 的空間。然而,因為 next 的空間長度改變了,是以程式又必須檢查 next 的後繼節點 —— next+1 ,看它的 pre_entry_length 能否編碼 next 的新長度,如果不能的話,程式又需要繼續對 next+1 進行擴容。。。

這就是說,在某個/某些節點的前面添加新節點之後,程式必須沿着路徑挨個檢查後續的節點,是否滿足新長度的編碼要求,直到遇到一個能滿足要求的節點(如果有一個能滿足,則這個節點之後的其他節點也滿足),或者到達 ziplist 的末端 zlend 為止,這種檢查操作的複雜度為 。

不過,因為隻有在新添加節點的後面有連續多個長度接近 254 的節點時,這種連鎖更新才會發生,是以可以普遍地認為,這種連鎖更新發生的機率非常小,在一般情況下,将添加操作看成是 複雜度也是可以的。

執行完這三種情況的其中一種後,程式更新 ziplist 的各項屬性,至此,添加操作完成。

注解

在第三種情況中,程式實際上是可以執行類似于情況二的動作的:它可以挨個地檢查新節點之後的節點,嘗試收縮它們的空間長度,不過 Redis 決定不這麼做,因為在一些情況下,比如前面提到的,有連續多個長度接近 254 的節點時,可能會出現重複的擴充——收縮——再擴充——再收縮的抖動(flapping)效果,這會讓操作的性能變得非常差。

删除節點

删除節點和添加操作的步驟類似。

1) 定位目标節點,并計算節點的空間長度 target-size :

target start here
           |
           V
+----------+----------+----------+----------+----------+----------+
|          |          |          |          |          |          |
|   prev   |  target  |   next   | next + 1 | next + 2 |   ...    |
|          |          |          |          |          |          |
+----------+----------+----------+----------+----------+----------+

           |<-------->|
            target-size
      

2) 進行記憶體移位,覆寫 target 原本的資料,然後通過記憶體重配置設定,收縮多餘空間:

target start here
           |
           V
+----------+----------+----------+----------+----------+
|          |          |          |          |          |
|   prev   |   next   | next + 1 | next + 2 |   ...    |
|          |          |          |          |          |
+----------+----------+----------+----------+----------+

           | <------------------------------------------ memmove
      

3) 檢查 next 、 next+1 等後續節點能否滿足新前驅節點的編碼。和添加操作一樣,删除操作也可能會引起連鎖更新。

周遊

可以對 ziplist 進行從前向後的周遊,或者從後先前的周遊。

當進行從前向後的周遊時,程式從指向節點 e1 的指針 p 開始,計算節點 e1 的長度(e1-size),然後将 p 加上 e1-size ,就将指針後移到了下一個節點 e2 。。。如此反覆,直到 p 遇到 ZIPLIST_ENTRY_END 為止,這樣整個 ziplist 就周遊完了:

p + e1-size + e2-size
                 p + e1-size     |
           p          |          |
           |          |          |
           V          V          V
+----------+----------+----------+----------+----------+----------+----------+
| ZIPLIST  |          |          |          |          |          | ZIPLIST  |
| ENTRY    |    e1    |    e2    |    e3    |    e4    |   ...    | ENTRY    |
| HEAD     |          |          |          |          |          | END      |
+----------+----------+----------+----------+----------+----------+----------+

           |<-------->|<-------->|
             e1-size    e2-size
      

當進行從後往前周遊的時候,程式從指向節點 eN 的指針 p 出發,取出 eN 的 pre_entry_length 值,然後用 p 減去 pre_entry_length ,這就将指針移動到了前一個節點 eN-1 。。。如此反覆,直到 p 遇到 ZIPLIST_ENTRY_HEAD 為止,這樣整個 ziplist 就周遊完了。

p - eN.pre_entry_length
                                            |
                                            |          p
                                            |          |
                                            V          V
+----------+----------+----------+----------+----------+----------+----------+
| ZIPLIST  |          |          |          |          |          | ZIPLIST  |
| ENTRY    |    e1    |    e2    |   ...    |   eN-1   |    eN    | ENTRY    |
| HEAD     |          |          |          |          |          | END      |
+----------+----------+----------+----------+----------+----------+----------+
      

查找元素、根據值定位節點

這兩個操作和周遊的原理基本相同,不再贅述。

小結

  • ziplist 是由一系列特殊編碼的記憶體塊構成的清單,可以儲存字元數組或整數值,同時是哈希鍵、清單鍵和有序集合鍵的底層實作之一。
  • ziplist 典型分布結構如下:
    area        |<---- ziplist header ---->|<----------- entries ------------->|<-end->|
    
    size          4 bytes  4 bytes  2 bytes    ?        ?        ?        ?     1 byte
                +---------+--------+-------+--------+--------+--------+--------+-------+
    component   | zlbytes | zltail | zllen | entry1 | entry2 |  ...   | entryN | zlend |
                +---------+--------+-------+--------+--------+--------+--------+-------+
                                           ^                          ^        ^
    address                                |                          |        |
                                    ZIPLIST_ENTRY_HEAD                |   ZIPLIST_ENTRY_END
                                                                      |
                                                             ZIPLIST_ENTRY_TAIL
          
  • ziplist 節點的分布結構如下:
    area        |<------------------- entry -------------------->|
    
                +------------------+----------+--------+---------+
    component   | pre_entry_length | encoding | length | content |
                +------------------+----------+--------+---------+
          
  • 添加和删除 ziplist 節點有可能會引起連鎖更新,是以,添加和删除操作的最壞複雜度為 ,不過,因為連鎖更新的出現機率并不高,是以一般可以将添加和删除操作的複雜度視為 。

繼續閱讀