redis原始碼分析 intset 整型集合

2021-07-16 14:44:23 字數 4591 閱讀 3433

intset,一種特殊的set資料結構,由多個整型元素組成。intset也是乙個有序整型集合,其內部設計非常精巧。

與往常一樣,先了解intset資料結構:

typedef

struct intset intset;

intset結構由encodinglengthcontents3個屬性構成。

encoding: 顧名思義,intset編碼。redis根據整型位數將intset分為intset_enc_int16intset_enc_int32intset_enc_int64三種編碼。

encoding

型別位元組

intset_enc_int16

int16_t

2intset_enc_int32

int32_t

4intset_enc_int64

int64_t8

length: 集合元素大小,時間複雜度由o(n)->o(1)。

contents: 元素陣列。

上文簡單介紹intset資料結構,下面將通過幾個主要的api深入分析其內部原理。

intsetnew:建立乙個整型集合,指定預設編碼為intset_enc_int16

intset *intsetnew(void)
intsetadd: 將新元素新增至集合,成功返回 1,失敗返回0。

intset *intsetadd(intset *is, int64_t value, uint8_t *success)  else         

is = intsetresize(is,intrev32ifbe(is->length)+1);

//移動pos之後的元素,為新元素騰出空間

if (pos < intrev32ifbe(is->length)) intsetmovetail(is,pos,pos+1);

}_intsetset(is,pos,value);

is->length = intrev32ifbe(intrev32ifbe(is->length)+1);

return is;

}

intsetadd執行流程如下:

a). 首先, 通過呼叫_intsetvalueencoding得到value合適的編碼valenc

if (v < int32_min || v > int32_max)

return intset_enc_int64;

else

if (v < int16_min || v > int16_max)

return intset_enc_int32;

else

return intset_enc_int16;

b). 其次, 比較新元素的編碼valenc與 當前集合的編碼is->encoding1).valenc>is->encoding: 表明當前集合無法儲存新元素,需對集合進行公升級。

2).valenc<=is->encoding:此時,集合無需公升級。優先判斷新元素是否存在。若存在,則return; 反之,擴充集合空間,為新元素騰出位置,最後新增新元素。

intsetadd內部呼叫多個函式,其中intsetupgradeandaddintsetsearchintsetmovetail尤為關鍵,在整個intset相關操作中,起到「承上啟下」的作用,能突出地體現intset的設計思想與靈魂。

intsetupgradeandadd: 僅供內部使用,其功能是對集合進行公升級,並新增新元素。

static intset *intsetupgradeandadd(intset *is, int64_t value)
執行流程:擴充集合空間(intsetresize)->集合元素設定新編碼(_intsetset)->向集合頭/尾部新增新元素(_intsetset)。

新元素的位置是由prepend變數控制。而prepend是根據 value 值定義的。

當value<0 時,表明 value 小於當前集合所有元素 , 系統將 value 作為集合第乙個元素。

當value>=0時, 表明 value 大於當前集合所有元素 , 系統將 value 新增到集合的尾部。

intsetsearch: 查詢集合元素,判斷value是否已存在。若元素存在,則返回1; 反之, 返回0。

static uint8_t intsetsearch(intset *is, int64_t value, uint32_t *pos)  else  else

if (value < _intsetget(is,0))

}//採用二分查詢演算法進行搜尋

while(max >= min) else

if (value < cur) else

}if (value == cur) else

}

intsetsearch使用」二分查詢」演算法「進行搜尋。

因此,可以推斷出,intset是一種有序的集合。當元素存在時,pos代表該元素所在的集合下標。當元素不存在時,pos表示該元素新增的位置。

intsetmovetail: 同樣僅供內部呼叫,其實現「移動集合元素」的功能。

static

void intsetmovetail(intset *is, uint32_t from, uint32_t to) else

if (encoding == intset_enc_int32) else

memmove(dst,src,bytes);

}

intsetmovetail將集合from位置之後的元素 移至 to位置,內部使用c語言memmove函式保證移動過程中資料的完整性。下面給出簡單的例項。

回顧intsetadd整個流程,可以看出集合始終保持有序的結構。內部使用二分查詢演算法,定位新元素的位置。

當新元素編碼 > 集合編碼時,表明集合需要公升級,同時根據新元素值來確定所新增的位置(頭部或尾部)。

當新元素編碼<=集合編碼時,表明集合無需公升級,系統會將集合大於value的元素往後移動一位,為新元素騰出空間。

intsetremove:從集合移除某個元素。

intset *intsetremove(intset *is, int64_t value, int *success) 

return is;

}

從上面的**可以看出,intsetremove設計非常巧妙。首先,通過判斷value編碼過濾一部分無效的搜尋。當value編碼在集合範圍內時,才會intsetsearch進行查詢。當找到元素時,將pos之後的元素向前移動乙個單元。最後,重置集合大小並設定集合長度。

intsetfind :從集合查詢元素,內部呼叫intsetsearch方法。

uint8_t intsetfind(intset *is, int64_t value)
intset內部只有」編碼公升級」的過程,沒有」降級」的操作。當將唯一乙個高位元素從將集合移除時,此時,集合不會轉換為低位編碼集合。

1. intset 是一種有序的整型集合,共有 `intset_enc_int16`、`intset_enc_int32`、`intset_enc_int64` 編碼型別。

2. intset `length` 屬性記了錄集合的大小。

3. intset 內部採用`二分查詢演算法` 定位元素。

4. intset 只會公升級,不會降級。當將乙個高位元素新增到低編碼集合時,此時,需要對集合進行公升級。

Redis原始碼分析 intset h c

intset.h c 是redis 的整數set實現,intset的結構體如下 基本結構 typedef struct intset intset intset的第乙個成員encoding,表明contents中的儲存資料的資料長度,可以是16bits,32bits,64bits。第二個成員leng...

Redis原始碼分析系列

redis目前熱門nosql記憶體資料庫,量不是很大,本系列是本人閱讀redis原始碼時記錄的筆記,由於時間倉促和水平有限,文中難免會有錯誤之處,歡迎讀者指出,共同學習進步,本文使用的redis版本是2.8.19。redis之hash資料結構 redis之intset資料結構 redis之skipl...

redis原始碼分析 adlist

typedef struct listnode listnode 首先定義了乙個節點,包含前驅和後繼以及對應的value typedef struct listiter listiter list的迭代器,next指標和迭代方向 typedef struct list list 鍊錶內容 head和...