intset,一種特殊的set資料結構,由多個整型元素組成。intset也是乙個有序整型集合,其內部設計非常精巧。
與往常一樣,先了解intset資料結構:
typedef
struct intset intset;
intset結構由encoding
、length
、contents
3個屬性構成。
encoding
: 顧名思義,intset編碼。redis根據整型位數將intset分為intset_enc_int16
、intset_enc_int32
、intset_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->encoding
。1).valenc
>is->encoding
: 表明當前集合無法儲存新元素,需對集合進行公升級。
2).valenc
<=is->encoding
:此時,集合無需公升級。優先判斷新元素是否存在。若存在,則return
; 反之,擴充集合空間,為新元素騰出位置,最後新增新元素。
intsetadd
內部呼叫多個函式,其中intsetupgradeandadd
、intsetsearch
、intsetmovetail
尤為關鍵,在整個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和...