redis 系列8 資料結構之整數集合
一.概述
整數集合(intset)是集合鍵的底層實現之一, 當一個集合只包含整數值元素,並且這個集合元素數量不多時, Redis就會使用整數集合作為集合鍵的底層實現。下面建立一個只包含5個元素的集合鍵,並且集合中所有元素都是整數值,那麼這個集合鍵的底層實現就會是整數集合。 接著新增非整數值,集合鍵的底層實現就會是hashtable。
127.0.0.1:6379> sadd numbers 1 3 5 7 9 (integer) 5 127.0.0.1:6379> object encoding numbers "intset" 127.0.0.1:6379> sadd numbers 'one' (integer) 1 127.0.0.1:6379> object encoding numbers "hashtable"
二. 整數集合實現
整數集合是Redis用於儲存整數值的集合抽象資料結構,它可以儲存型別為int16_t, int32_t, int64_t的整數值,並且保證集合中不會出現重複元素。資料集合定義如下:
// 每個intset.h/intset結構表示一個整數集合 typedef struct intset { //編碼方式 uint32_t encoding;//集合包含的元素數量 uint32_t length; //儲存元素的陣列 int8_t contents[]; }intset;
(1) contents陣列是整數集合的底層實現,整數集合的每個元素都是contents陣列的一個數組項(item),各個項在陣列中按值從小到大有序排列,並且陣列中不包含重複項。如下面指令碼:
127.0.0.1:6379> sadd record 5 3 4 5 6 0 (integer) 5 127.0.0.1:6379> smembers record1) "0" 2) "3" 3) "4" 4) "5" 5) "6"
(2) length屬性記錄了整數集合包含的元素數量,也即是contents陣列的長度。雖然contents屬性宣告為int8_t型別的陣列,但實現上contents陣列並不儲存任何int8_t型別的值,contents陣列的真正型別取決於encoding屬性的值。
a. 如果encoding 屬性的值為intset_enc_int16,那麼contents就是一個int16_t型別的陣列,數組裡的每個項都是一個int16_t型別的整數值(範圍在 -32768 ~ 32767)。如下圖encoding屬性的值有5個整數型,根據這些整數值得出encoding為int16_t型別。
b. 如果encoding屬性的值為intset_enc_int32, 那麼數組裡每個項就是一個int32_t型別的整數值(範圍在 -2147483648 ~ 2147483647)。還有encoding屬性的值為intset_enc_int64型別的,數組裡每個項取取值範圍更大。
需要注意的是:假設contents陣列儲存的值為2147483647, 1,2,3 四個整數值。 但只有第一個整數值需要用int32_t型別來儲存,而其它三個值可以用int16_t型別來儲存。不過根據整數集合的升級規則,當一個底層的int16_t陣列的整數集合新增一個int64_t型別的整數值時,整數集合中所有元素都會被轉換成int64_t型別。 所以contents陣列儲存的整數值都是int64_t型別的。
三. 升級
當我們要將一個新元素新增到整數集合裡面,並且新元素的型別比整數集合現有所有元素的型別都要長時,整數集合需要先進行升級,然後才能將新元素新增到整數集合中。假設:集合中包含三個int16_t型別的元素,值分別是1,2,3 。因為每個元素都佔用16位空間,所以整數集合底層陣列的大小 為3 * 16 =48位。現將int32_t的數值65535新增進去,這裡程式需要對整數集合進行升級。
升級整數集合並新增新元素共分三步進行:
(1) 根據新元素的型別,擴充套件整數集合底層陣列的空間大小 ,併為新元素分配空間。分配空間後,現在整數集合4個元素的底層陣列大小為4 *32 =128位, 此時前三位還是48位空間,如下圖所示:
(2) 將底層陣列現有的所有元素都轉換成與新元素相同的型別(需要從int16_t 轉成int32_t所需的空間) ,轉換後元素位置有序不變,如下圖所示:
(3) 將新元素新增到底層數組裡面,如下圖所示:
四. 升級的好處
4.1 提升靈活性
為了避免型別錯誤,通常不會將兩種不同型別的值放在同一個資料結構裡面,通過升級處理可以隨意地將int16_t, int32_t, , int64_t 型別的整數新增到集合中,而不必擔心出現型別錯誤。
4.2 節約記憶體
要讓一個數組可以同時儲存int16_t,int32_t, , int64_t三種類型的值,最簡單的做法就是直接使用int64_t型別的陣列作為整數集合的底層實現,不過這樣浪費記憶體空間。
五. 降級
整數集合不支援降級操作,一旦對陣列進行了升級,編碼就會一直保持升級後的狀態。即使集合裡只有一個需要使用int64_t型別的元素被刪除了,整數集合的編碼仍然會維持intset_enc_int64, 底層陣列也仍然會是int64_t型別,如下圖所示:
六. 整數集合API
函式 |
作用 |
intsetNew |
建立一個新的壓縮列表 |
intsetAdd |
將給定元素新增到整數集合裡面 |
intsetRemove |
從整數集合中移除給定元素 |
intsetFind |
檢查給定值是否存在於集合 |
intsetRandom |
從整數集合中隨機返回一個元素 |
intsetGet |
取出底層陣列在給定索引上的元素 |
intsetLen |
返回整數集合包含的元素個數 |
intsetBloblen |
返回整數集合佔用的記憶體位元組數 |