Redis學習筆記 原始碼閱讀 整數集合

2021-10-08 00:15:37 字數 3804 閱讀 9647

整數集合(intset)是乙個有序的、儲存整型資料的結構,當redis集合型別的元素都是整數並且都處在64位有符號整數範圍之內時,使用該結構體儲存。

在兩種情況下,底層編碼會發生轉換。

整數集合在redis中可以儲存int16_t、int32_t、int64_t型別的整型資料,並且可以保證集合中不會出現重複資料。每個整數集合使用乙個intset型別的資料結構表示。intset結構體表示如下:

encoding

length

element1

element2

element3

其原始碼定義如下:

typedef

struct intset intset;

encoding:編碼型別,決定每個元素占用幾個位元組。有如下3種型別。

intset_enc_int16:當元素值都位於int16_min和int16_max之間時使用。該編碼方式為每個元素占用2個位元組。

intset_enc_int32:當元素值位於int16_max到int32_max或者int32_min到int16_min之間時使用。該編碼方式為每個元素占用4個位元組。

intset_enc_int64:當元素值位於int32_max到int64_max或者int64_min到int32_min之間時使用。該編碼方式為每個元素占用8個位元組。

在原始碼中對應的定義如下:

#define intset_enc_int16 (sizeof(int16_t))

#define intset_enc_int32 (sizeof(int32_t))

#define intset_enc_int64 (sizeof(int64_t))

contents:儲存具體元素,根據encoding欄位決定多少個位元組表示乙個元素

接下來我們看下整數集合的基本操作。

先看**:

uint8_t intsetfind

(intset *is, int64_t value)

整數集合中的所有元素的編碼型別都是一樣的,所有在查詢前我們可以先判斷查詢value的編碼型別是否超過當前編碼型別,超過的話說明查詢的value不可能在整數集合中,縮短無效查詢的耗時。_intsetvalueencoding的邏輯很簡單,我們看下原始碼:

static uint8_t _intsetvalueencoding

(int64_t v)

這麼簡單的邏輯直接忽略,如果編碼沒有超過當前編碼則開始查詢value,看intsetsearch的原始碼:

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

}

這段**的核心其實就是乙個二分查詢,二分查詢前做一些基本判斷,intset是否為空,value是否比intset最大值大,是否比intset最小值小,並更新相應的插入位置,如果value的值介於最小值和最大值之間,可使用二分查詢的思想來定位value的位置,二分查詢我們就不說了吧。然後按照查詢結果返回資料。

我們知道整數集合中所有元素的編碼都是一致的,那麼如果集合中的資料都是很小的,比如不超過100,這個時候集合的編碼格式就是intset_enc_int16,但是如果此時存入了乙個比較大的值,比如220,或者-220,此時intset_enc_int16是儲存不了,那麼集合就會公升級編碼到intset_enc_int32,當前所有的元素占用的儲存空間都要對齊到4位元組,而不是之前的2位元組了。

會不會有人問為什麼不能只把當前的值用4個位元組,其餘的依然保留2位元組,整數集合的存在不是為了節約記憶體嘛?如果你這麼幹了後面查詢就沒辦法按照固定的offset去獲取元素值了,可以參考下壓縮列表的設計做比對。

這麼看新增元素有兩種,一種是不需要做元素公升級操作,一種是需要做公升級元素操作的,我們先看簡單的。

redis中是沒有降級元素操作的,因為刪除元素時判斷是否縮減還需要遍歷一次。

我們先看下原始碼片段:

if

(intsetsearch

(is,value,

&pos)

) 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

);

首先查詢value,如果存在,直接返回;否則的話,就先擴容intset,知道為什麼intset不用來存在大量元素了吧,每次插入都是要做記憶體resize的。

記憶體申請完了,是不是將插入位置pos後面的資料全部右移乙個單位讓給value,我們的intsetmovetail就是幹這個事情的,將pos起始的所有資料全部移到以pos+1起始處,然後再講value插入到pos處,更新intset的長度資訊。

既然需要公升級儲存編碼,那麼插入value和intset中當前的值之間只會有兩種情況,第一,插入的值是正數且比intset中最大值還要大,那麼插入到intset最後面;第二,插入的值是負數且比intset最小值還要小,那麼插入到intset最前面。只能有這兩種情況。帶著這個認知我們看下原始碼:

static intset *

intsetupgradeandadd

(intset *is, int64_t value)

當value小於0,說明插入位置在前面,當value大於0,說明插入位置在後面。在resize後進行while迴圈賦值,當value小於0,prepend=1,length+1說明起始位置是resize後的最後乙個元素,因為value小於0,插入位置是前面嘛,後面不用留空間,當時value大於0時就需要預留位置了,所以prepend=0,從length位置開始插入(resize後長度變成length+1了)。後面再按照prepend判斷插入位置,更新完值後再更新下length就結束了。

while中倒序賦值的原因我想不用細說都知道原因嗎?唯獨有一點,在賦值過程中會不會出現舊值還沒來得及賦值到新位置就被覆蓋了?不會的,我算過了,只有當賦值到最後乙個元素,覆蓋位置才會追上,所以不用擔心。

先看下原始碼:

intset *

intsetremove

(intset *is, int64_t value,

int*success)

return is;

}

刪除前也做一次編碼格式的檢測,如果能夠查詢到value就講value所在pos後面的有效記憶體全部向前移動乙個儲存單位,這就是intsetmovetail做的事情,然後通過resize縮減記憶體,更新length,刪除操作就結束了。

redis原始碼閱讀筆記

在redis中乙個資料庫結構體是這樣的 每個dict是乙個hash表 typedef struct redisdb redisdb dict欄位中存放以key值為鍵,以value指標為值的hash表項dict根據型別的不同分為如下幾種 1 字串 string 操作 set key value get...

redis原始碼學習筆記

目錄 1 從資料結構開始 圖為原始碼,附帶個人簡單分析 a 動態字串 檔案 sds.h sds.c 前言 s sizeof struct sdshdr 的解釋為buf為柔性陣列,不占用空間,僅僅為偏移量,所以s指標向後退乙個結構體大小為結構體位址所在。分析 這個結構是整個動態字串的基礎,sds為 s...

閱讀筆記 fsnotify原始碼閱讀

fsnotify的github位址是 fsnotify是乙個資料夾監控應用。可以使用建立乙個watcher來對某個資料夾進行監控 檔案目錄很簡單,實際就兩個程式檔案,fsnotify.go 和 各平台的fsnotify go 後乙個檔案是各個不同平台的實現 example test.go中給的是最簡...