redis提供一種叫整數集合的資料結構,當資料中只包含整數,並且資料數量不多時,redis便會採用整數集合儲存
redis保證整數集合有以下幾個特性
所含元素全是整數,且不重複
內部元素有序,通常是會從小到大排序
內部編碼統一,盡可能採用合適的編碼儲存資料
當編碼不合適時,執行公升級操作
接下來會針對上述幾個特性分別進行分析,可以看到,整數集合有點類似連續陣列,只是在某種程度上新增了編碼,同時為了編碼統一,會有公升級相關操作
redis提供sadd命令向資料庫中新增整數集合
127.0.0.1:6379> sadd digits 1 2 3 4 5 //向資料庫中新增整數集合
(integer) 5
127.0.0.1:6379> smembers digits //獲取整數集合digits的成員
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> object encoding digits //獲取digits內部儲存結構
"intset"
127.0.0.1:6379>
不過,有以下幾種情況redis會更改底層的儲存結構,將整數集合改為雜湊表
a.元素個數過多時
b.存在非整數元素時
當採用sadd向整數集合中新增元素,但是其中包含乙個字串型別資料時,那麼再次獲取內部儲存結構時會發現返回的是」hashtable」而非」intset」
127.0.0.1:6379> sadd digits-str 1 2 3 a 4 5 //其中帶有字串a
(integer) 6
127.0.0.1:6379> smembers digits-str //獲取元素
1) "1"
2) "3"
3) "2"
4) "4"
5) "a"
6) "5"
127.0.0.1:6379> object encoding digits-str //獲取內部儲存結構,發現是雜湊表
"hashtable"
127.0.0.1:6379>
1.儲存結構
前面也提高過,整數集合很像連續陣列,內部儲存的是整數。但是redis為整數集合新增了編碼的功能,也就是型別。可以根據元素大小,選擇合適的編碼來儲存,當然,是為了節約記憶體
redis提供三種編碼,分別對應int16_t,int32_t,int64_t三種型別,對於採用int16_t就可以儲存的資料,採用int32_t或int64_t就顯得過於浪費了,這便是編碼的實際作用,採用最適合的型別儲存資料
#define intset_enc_int16 (sizeof(int16_t))
#define intset_enc_int32 (sizeof(int32_t))
#define intset_enc_int64 (sizeof(int64_t))
整數集合的定義如下,其中儲存著內部元素的編碼,元素個數,和元素陣列
/* 整數集合,用於節約記憶體 */
需要注意的是,整數集合中的所有資料的編碼都是一樣的,也就是說,如果其中乙個元素的編碼要改變,所有元素的編碼都需要同時改變,這一點在後面新增元素時會看到
另外,雖然contents陣列中的元素型別是int8_t型別,但是這並不代表資料是這個型別的。int8_t只是為了用最小的型別記錄資料,在存放資料時,乙個資料可以同時占用多個int8_t(這是由於內部資料的位址空間是連續的)
2.整數集合相關操作
2.1新增資料
新增資料功能由intsetadd函式完成,函式內部首先判斷要新增的資料是否能被當前編碼儲存,如果不能,則需要將整個集合的資料重新改寫編碼,也就是公升級操作
/* 在整數集中新增乙個元素 */
/* 根據編碼長度判斷是否需要先執行公升級操作再新增 */
intset *intsetadd(intset *is, int64_t value, uint8_t *success) else
/* 擴充集合,為新資料分配空間 */
is = intsetresize(is,intrev32ifbe(is->length)+1);
/* 為了保證集合中元素有序,需要執行移動操作 */
if (pos < intrev32ifbe(is->length)) intsetmovetail(is,pos,pos+1);
}/* 將新資料放在pos位置 */
_intsetset(is,pos,value);
/* 更新集合資料個數 */
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
2.2移動資料
intsetmovetail函式用於將源下標開始的資料移動到目的下標處
/* 將整數集合中從from開始的資料移動到to位置 */
static void intsetmovetail(intset *is, uint32_t from, uint32_t to) else if (encoding == intset_enc_int32) else
/* memmove,記憶體移動操作 */
memmove(dst,src,bytes);
}
2.3公升級操作
如果不考慮公升級操作,新增函式還是比較容易理解的。找到新資料的位置,然後移動元素,將新元素放到它的位置上,這些操作和陣列的新增操作非常像。
前面說過,編碼的加入是為了節約記憶體占用,但是帶來的問題就是內部編碼統一,整個集合都需要採用相同的編碼儲存資料,那麼當乙個資料無法被當前編碼儲存時,就需要將整個集合的編碼公升級,這就導致所有原有資料的編碼也要被改變
舉例來說,假設之前採用int16_t就可以儲存所有資料,此時需要乙個int32_t型別才能儲存的資料,那麼就需要將以前的資料都改為int32_t型別以保證編碼統一
編碼統一的原因是整數集合內部採用陣列儲存資料,每個資料的大小都必須是一樣的,這樣才可以通過偏移量(下標)來獲取資料
intsetupgradeandadd函式先將集合編碼公升級,然後再新增資料
/* 將資料插入到整數集中,如果當前整數集的編碼不足以容納value,那麼將整數集執行公升級操作 */
/* 公升級操作是將整數集的編碼加大,這需要將原有資料的編碼也進行加大 */
static intset *intsetupgradeandadd(intset *is, int64_t value)
redis的整數集合不支援降級操作,也就是一旦將編碼調高,就無法將其降低,這是沒有辦法的事情,因為如果要降級,就需要遍歷資料判斷是否需要降級,這個操作是十分耗時的
3.物件系統中的整數集合
整數集合在物件系統中作為集合的底層實現
/* 建立整數集合物件 */
robj *createintsetobject(void)
4.小結
整數集合部分還是很容易理解的,實際上就是陣列外套乙個編碼,根據編碼統一適當進行公升級操作。另外,整數集合作為集合的底層實現,保證了資料的有序性,無重複性,但是只適用於資料個數較少,且都是整數的情況,當資料個數很多,或者存在其他型別的資料(如字串)時,redis會採用hashtable作為集合的底層實現
redis原始碼學習之整數集合
intset的底層實現比較簡單,因為它所有的key都是整型,只是整型分為16bits 32bits和64bits這三種型別,當新插入的元素比當前集合中所有數還要長時,就要進行公升級了,這部分原始碼很簡單,主要就是公升級部分。intset 的編碼方式 define intset enc int16 s...
Redis原始碼解析 06整數集合
整數集合 intset 是集合鍵的底層實現之一,當乙個集合只包含整數值元素,並且這個集合的元素數量不多時,redis就會使用整數集合作為集合鍵的底層實現。intset可以儲存型別為int16 t,int32 t,int64 t的整數值,並且保證集合中不會出現重複元素。整數集合的結構體定義在intse...
Redis學習筆記 原始碼閱讀 整數集合
整數集合 intset 是乙個有序的 儲存整型資料的結構,當redis集合型別的元素都是整數並且都處在64位有符號整數範圍之內時,使用該結構體儲存。在兩種情況下,底層編碼會發生轉換。整數集合在redis中可以儲存int16 t int32 t int64 t型別的整型資料,並且可以保證集合中不會出現...