Redis底層資料結構 整數集合

2021-10-12 04:56:31 字數 4076 閱讀 9420

redis 中的整數集合 intset 是用來儲存多個不重複的整數值且有序的集合抽象資料結構,可以儲存型別為 int16-t 、int32-t 或者 int64-t 的整數值。

整數集合在 redis 中作為了集合 set 資料結構的底層實現之一。

當乙個集合中的元素都是整數值,且元素不多的時候,整數集合就會作為集合 set 的底層實現。

整數集合結構的具體**:

typedef struct intset  intset;
屬性 encoding

屬性 encoding 代表著整數集合儲存整數值時,所使用的儲存型別長度。

encoding 的值可以為下面三種常量型別其中之一。

define intset_enc_int16 (sizeof(int16_t))

define intset_enc_int32 (sizeof(int32_t))

define intset_enc_int64 (sizeof(int64_t))

可以把這三種型別理解為可以儲存16位範圍內的整數、32位範圍內的整數、

64位範圍內的整數。

屬性 contents

屬性 contents 是乙個陣列,整數集合的每個元素都儲存在了 content 陣列裡對應的下標位置裡,他們按數值從小到大排序儲存,且不含重複的整數值。

可以看到 contents 陣列被宣告為 int8_t 型別,但實際上 contents 陣列並不儲存任何 int_8 元素,只是作為了乙個佔位符來使用。到真正讀寫資料的時候,程式會根據 encoding 值來進行對 contents 陣列進行型別轉換和指標運算。

整數集合保證了集合中的所有元素只能為同一種 encoding 型別,其 encoding 型別由集合中元素的絕對值最大的那個元素的長度型別決定。

屬性 length

屬性 length 代表了 contents 陣列的長度,儲存了這個整數集合的元素數量。

下圖為乙個 int16_t 型別的整數集合:

當我們在乙個 encoding 型別為 int16_t 的整數集合新增乙個新的整數值的時候,假如這個整數值型別大於int16_t 型別,整數集合將進行公升級操作,再將新的整數值新增進整數集合。

具體公升級內容:

下面是乙個 int16_t 型別的整數集合。

因為有四個元素,加上每個元素占用了16位長度的記憶體空間,所以已經這個整數集合已經占用了64位記憶體空間。

當乙個新的整數值 66666 要新增進來的時候,因為 66666 已經超過了原本的 int16_t 型別16位最大範圍的整數值,它的型別為 int32_t 了,所以此時需要公升級整數集合來接納新的整數值。

具體公升級步驟

1.根據新整數值的型別長度以及集合最終元素的的數量,對底層 contents 陣列進行空間分配

新整數值的型別為 int32_t ,所以乙個整數值將佔32位記憶體空間,以及集合最終元素的數量為5個,所以程式將重新分配160位的記憶體空間,整數集合記憶體空間從原本的64位公升級到了160位。

2.公升級整數集合原本元素的型別,然後重新排序,保證整數集合的有序性

將原本的整數值的型別進行公升級到 int32_t 型別,並將轉換後的整數值放置到對應的位置,保證整數值在底層陣列裡面還是有序性。

3.將新的整數值新增到公升級之後的整數集合 contents 陣列裡面去

最後將整數集合的 encoding 屬性值設定為 intset_enc_int32 , 然後將 66666 這個整數值新增進整數集合。

最終新增完成的整數集合:

公升級優點

1.提公升靈活性

因為 c 語言是靜態型別的語言,我們會為不同型別的值賦予不同的型別,就必須先強行為值定義型別。這樣的話,整數集合的公升級策略避免了這種預先正確定義型別,通過乙個適當的型別,接受所有新增進來的整數值。

2.節約記憶體

通過公升級策略可以一定達到節約記憶體的目的,因為可以用佔記憶體較少的型別 int16_t 來儲存整數值,當有需要的時候,再來公升級型別。

建立集合操作

建立集合具體**:

intset *intsetnew(void)
程式首先呼叫 zmalloc 函式為整數集合分配記憶體,分配之後將 encoding 屬性值設定為 int16_t 型別,這樣可以用最低的記憶體開局,不用一開始占用很大的記憶體空間,接著把 length 屬性值設定為0,就此建立成功

插入元素操作

插入元素具體**:

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);

}

// 將新值設定到底層陣列的指定位置中

_intsetset(is,pos,value);

// 增一集合元素數量的計數器

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

return is;

}

插入元素的時候,會先計算新元素所需的長度,然後來到**中的第二個 if 判斷是否需要公升級操作。

如果新元素的編碼型別比原來整數集合的 encoding 值大,那麼執行 intsetupgradeandadd 函式進行集合公升級操作,之後將公升級後的整數集合返回。

不滿足公升級操作的話,先查詢新元素是否在原來的整數集合存在,如果有的話,操作失敗,返回原來的整數集合。這裡是為了保證整數集合的元素唯一性。沒有的話,就會為集合調整新的記憶體空間,然後將新元素設定進他合適的位置上。

之後為 length 屬性值加一,返回新的整數集合,完成了插入操作。

查詢元素操作

查詢元素具體**:

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

}

查詢元素開始的時候,先對整數集合是否有值進行判斷,沒值就返回0。有值就通過獲取首尾元素的值來判斷該元素是否存在集合中,因為整數集合的有序性,通過最大最小值可以直接判斷出是否存在。

然後通過陣列的二分查詢思想的**,快速查詢該元素的位置,最終找到了位置就返回1表示找到並將找到的位置設定到 pos 屬性,0表示沒找到。

刪除元素操作

刪除元素具體**:

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

return is;

}

刪除元素的過程與增加元素的過程有些類似,也是通過先計算需要刪除元素的編碼型別,只有當元素的編碼型別小於等於整數集合的 encoding 的時候(因為大於的話,表示該元素不存在整數集合中),且呼叫 intsetsearch 函式查詢元素存在,才執行具體的刪除操作。

具體刪除操作的時候,呼叫 intsetmovetail 函式將原來這個元素的位置後面的元素往前移動。

最後重新調整集合的記憶體空間,以及集合的長度完成了最終的刪除操作。

通過 redis 中的整數集合這樣乙個唯一有序的資料結構,講述了整數集合在 redis 中的底層結構、公升級操作以及相關實現。

參考:《 redis設計與實現 》

Redis底層資料結構 五 整數集合

整數集合是集合鍵的底層實現之一。整數集合 intset 是redis用於儲存整數值的集合抽象資料結構,可以儲存型別為int16 t,int32 t或者int64 t的整數值,並且保證集合中不會出現重複元素。contents陣列是整數集合的底層實現 證書集合的每個元素都是contents陣列乙個陣列項...

Redis底層資料結構?

福哥口訣法 簡鏈字跳整 壓快壓 sds synamic string 簡單動態字串。支援自動動態擴容的位元組陣列 list 鍊錶 雙端鍊錶。dict 字典。使用雙雜湊表實現的,支援平滑擴容的字典 zskiplist 跳躍表。附加了後向指標的跳躍表 intset 整數集合。用於儲存整數數值集合的自有結...

Redis底層資料結構

redis底層實現的8種資料結構 sds synamic string 支援自動動態擴容的位元組陣列 list 鍊錶 dict 使用雙雜湊表實現的,支援平滑擴容的字典 zskiplist 附加了後向指標的跳躍表 intset 用於儲存整數數值集合的自有結構 ziplist 一種實現上類似於tlv,但...