圖解Redis之資料結構篇——壓縮列表
前言
同整數集合一樣壓縮列表也不是基礎資料結構,而是 Redis 自己設計的一種資料儲存結構。它有點兒類似陣列,通過一片連續的記憶體空間,來儲存資料。不過,它跟陣列不同的一點是,它允許儲存的資料大小不同。
一、壓縮列表
聽到“壓縮”兩個字,直觀的反應就是節省記憶體。之所以說這種儲存結構節省記憶體,是相較於陣列的儲存思路而言的。我們知道,陣列要求每個元素的大小相同,如果我們要儲存不同長度的字串,那我們就需要用最大長度的字串大小作為元素的大小(假設是20個位元組)。儲存小於 20 個位元組長度的字串的時候,便會浪費部分儲存空間。
陣列的優勢佔用一片連續的空間可以很好的利用CPU快取訪問資料。如果我們想要保留這種優勢,又想節省儲存空間我們可以對陣列進行壓縮。
但是這樣有一個問題,我們在遍歷它的時候由於不知道每個元素的大小是多少,因此也就無法計算出下一個節點的具體位置。這個時候我們可以給每個節點增加一個lenght的屬性。
如此。我們在遍歷節點的之後就知道每個節點的長度(佔用記憶體的大小),就可以很容易計算出下一個節點再記憶體中的位置。這種結構就像一個簡單的壓縮列表了。
二、Redis壓縮列表
壓縮列表(zip1ist)是列表和雜湊的底層實現之一。
當一個列表只包含少量列表項,並且每個列表項要麼就是小整數值,要麼就是長度比較短的字串,那麼Redis就會使用壓縮列表來做列表的底層實現。
當一個雜湊只包含少量鍵值對,比且每個鍵值對的鍵和值要麼就是小整數值,要麼就是長度比較短的字串,那麼Redis就會使用壓縮列表來做雜湊的底層實現。
2.1 Redis壓縮列表的構成
壓縮列表是Redis為了節約記憶體而開發的,是由一系列特殊編碼的連續記憶體塊組成的順序型(sequential)資料結枃。一個壓縮列表可以包含任意多個節點(entry),每個節點可以儲存一個位元組陣列或者一個整數值,如下圖。
示例:
如上圖,展示了一個總長為80位元組,包含3個節點的壓縮列表。如果我們有一個指向壓縮列表起始地址的指標p,那麼表為節點的地址就是P+60。
2.2 Redis壓縮列表節點的構成
每個壓縮列表節點可以儲存一個位元組陣列或者一個整數值。其中,位元組陣列可以是以下三種長度中的一種。
- 長度小於等於63(2^6-1)位元組的位元組陣列;
- 長度小於等於16383(2^14-1)位元組的位元組陣列
- 長度小於等於4294967295(2^32-1)位元組的位元組陣列
整數值可以是以下6種長度中的一種
- 4位長,介於0至12之間的無符號整數
- 1位元組長的有符號整數
- 3位元組長的有符號整數
- int16_t型別整數
- int32_t型別整數
- int64_t型別整數
節點的 previous_entry_length屬性以位元組為單位,記錄了壓縮列表中前一個節點的長度。 previous_entry_length屬性的長度可以是1位元組或者5位元組。
- 如果前一節點的長度小於254位元組,那麼 previous_entry_length屬性的長度為1位元組,前一節點的長度就儲存在這一個位元組裡面。
- 如果前一節點的長度大於等於254位元組,那麼 previous_entry_length屬性的長度為5位元組:其中屬性的第一位元組會被設定為0xFE(十進位制值254),而之後的四個位元組則用於儲存前一節點的長度.
節點的encoding屬性記錄了節點的content屬性所儲存資料的型別以及長度。
- 一位元組、兩位元組或者五位元組長,值的最高位為00、01或者10的是位元組陣列編碼這種編碼表示節點的 content屬性儲存著位元組陣列,陣列的長度由編碼除去最高兩位之後的其他位記錄。
- 一位元組長,值的最高位以11開頭的是整數編碼:這種編碼表示節點的content屬性儲存著整數值,整數值的型別和長度由編碼除去最高兩位之後的其他位記錄。
節點的content屬性負責儲存節點的值,節點值可以是一個位元組陣列或者整數,值的型別和長度由節點的encoding屬性決定。
- 編碼的最高兩位00表示節點儲存的是一個位元組陣列。
- 編碼的後六位001011記錄了位元組陣列的長度11。
- content屬性儲存著節點的值"hello world"。
- 編碼11000000表示節點儲存的是一個int16_t型別的整數值;
- content屬性儲存著節點的值10086
2.3 常用操作的時間複雜度
操作 | 時間複雜度 |
---|---|
建立一個新的壓縮列表 | O(1) |
建立一個包含給定值的新節點,並將這個新節點新增到壓縮列表的表頭或者表尾 | 平均O(N),最壞O(N^2)(可能發生連鎖更新) |
將包含給定值的新節點插人到給定節點之後 | 平均O(N),最壞O(N^2)(可能發生連鎖更新) |
返回壓縮列表給定索引上的節點 | O(N) |
在壓縮列表中査找並返回包含了給定值的節點 | 因為節點的值可能是一個位元組陣列,所以檢查節點值和給定值是否相同的複雜度為O(N),而查詢整個列表的複雜度則為(N^2) |
返回給定節點的下一個節點 | O(1) |
返回給定節點的前一個節點 | O(1) |
獲取給定節點所儲存的值 | O(1) |
從壓縮列表中刪除給定的節點 | 平均O(N),最壞O(N^2)(可能發生連鎖更新) |
刪除壓縮列表在給定索引上的連續多個 | 平均O(N),最壞O(N^2)(可能發生連鎖更新) |
返回壓縮列表目前佔用的記憶體位元組數 | O(1) |
返回壓縮列表目前包含的節點數量 | 點數量小於65535時為O(1),大於65535時為O(N) |
本文重點
壓縮列表是Redis為節約記憶體自己設計的一種順序型資料結構。
- 壓縮列表被用作列表鍵和雜湊鍵的底層實現之一。
- 壓縮列表可以包含多個節點,每個節點可以儲存一個位元組陣列或者整數值。
新增新節點到壓縮列表,或者從壓縮列表中刪除節點,可能會引發連鎖更新操作,但這種操作出現的機率並不高。
參考
《Redis設計與實現》
《Redis開發與運維》
《Redis官方文件》