您現在的位置是:首頁 > 籃球

深入理解Redis 資料結構—字典

  • 由 程式那點事 發表于 籃球
  • 2022-10-12
簡介取餘就是計算兩數相除的餘數, 比如一個數組長度為4,索引範圍是0~3,需要放置0,1,7,放置如下圖所示:舉個例子,要將一個鍵值對k0和v0新增到下方的空字典表中:首先計算鍵的雜湊值:hash = dict—>type->has

table鍵有什麼用

字典,又稱為

符號表、關聯陣列或對映

,是一種用於儲存

鍵值對

的抽象資料結構。在字典中,一個鍵可以和一個值進行關聯,這些關聯的鍵和值稱為鍵值對。鍵值對中鍵是

唯一的

,我們可以根據

鍵key

透過對映查詢或者更新對應的

值value

很多高階開發語言有對應集合支援字典這種資料結構,比如

Java

中的

Map

集合。

C語言

並未內建字典這種資料結構,

Redis

構建了自己的字典實現。

應用

字典在

Redis

中應用非常廣泛,

Redis

資料庫就是使用字典作為資料底層的實現。對資料的

增、刪、改、查

操作也是建立在字典之上操作。

當執行命令:

set msg “hello”

在資料庫中建立一個鍵為

msg

,值為

hello

的鍵值對,這個

鍵值對

就用字典來實現的。建立其他資料型別的鍵值對,比如

list

hash

set

sort set

也是用字典來實現的。

處理用來表示資料中的鍵值對,字典還是

hash

資料型別底層實現之一,比如一個

hash

資料型別

website

,包含

100

個鍵值對,這些鍵值對中的鍵是

網址名稱

,值是

網頁地址

redis> HGETALL website1)“Redis”2)“Redis。io”3)“nacos”4)“nacos。io”。。。。。

website

鍵的底層就是一個字典,包好了

100

鍵值對

,例如:

鍵值對中的鍵為

“Redis”

,值為

“Redis。io”

鍵值對中的鍵為

“nacos”

,值為

“nacos。io”

字典的實現

Redis

字典使用雜湊表作為底層實現,一個雜湊表裡面有多個雜湊表節點,每個雜湊表節點儲存字典中的鍵值對。

雜湊表

Redis

字典使用的雜湊表由

dict。h/dictht

結構來表示:

/* This is our hash table structure。 Every dictionary has two of this as we * implement incremental rehashing, for the old to the new table。 */typedef struct dictht { // table 陣列 dictEntry **table; // 雜湊表的大小 unsigned long size; // 等於size-1,用於計算索引值 unsigned long sizemask; // 已有的鍵值對數量 unsigned long used;} dictht;

註釋:這是雜湊表結構,每個字典有兩個實現增量重雜湊,從舊的雜湊表到新的雜湊表。

table

屬性的是一個數組,陣列中的每個元素都指向雜湊節點

dictEntry

,每個

dictEntry

結構都儲存一個鍵值對。

size

記錄了雜湊表的大小,也就是

table

陣列的大小。

used

屬性記錄了雜湊表目前已有的鍵值對數量。

sizemask

的值等於

size-1

,這個屬性和雜湊表一起決定鍵應該放在

table

陣列的那個位置上。

下圖展示一個大小為

4

空雜湊表(沒有包含任務的鍵值對):

深入理解Redis 資料結構—字典

雜湊表節點

雜湊表節點使用

dictEntry

結構來表示,每個

dictEntry

結構都儲存著一個

鍵值對

typedef struct dictEntry { // 鍵 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 指向下一個雜湊表節點,形成連結串列 struct dictEntry *next;} dictEntry;

其中:

key

儲存鍵值

v

保持值,

v

可以是一個指標,可以是

uint64_t

整數,也可以是一個

int64_t

整數。

next

指向另一個雜湊表節點的指標,這個指標將多個

雜湊值相同

的鍵值對連線在一起,以此解決

hash衝突

的問題。

下圖展示兩個

鍵的hash值

相同的雜湊表節點

k0

k1

,兩者透過

next

指標連線在一起。

深入理解Redis 資料結構—字典

字典

Redis

中的字典由

dict。h/dict

結構表示:

typedef struct dict { // 型別特定的函式 dictType *type; // 私有函式 void *privdata; // 雜湊表 dictht ht[2]; // rehash 索引 int rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */} dict;

type

屬性和

privadata

屬性是針對不同型別的鍵值對,為建立多型的字典而設定。

type

是指向

dictType

結構的指標,每個

dictType

包含幾組針對特定型別的鍵值對操作的函式,

Redis

會為用途不同的字典設定不同的函式。下圖展示

dictType

字典型別:

typedef struct dictType { // 計算雜湊值 unsigned int (*hashFunction)(const void *key); // 複製鍵 void *(*keyDup)(void *privdata, const void *key); // 複製值 void *(*valDup)(void *privdata, const void *obj); // 對比鍵 int (*keyCompare)(void *privdata, const void *key1, const void *key2); // 銷燬鍵 void (*keyDestructor)(void *privdata, void *key); // 銷燬值 void (*valDestructor)(void *privdata, void *obj);} dictType;

privdata

屬性儲存針對不同的型別操作的函式傳的可選引數。

ht[2]

是包含兩個數大小的陣列,型別為

dictht

雜湊表。字典只使用

ht[0]

雜湊表,ht

[1]

只會對

ht[0]

雜湊表進行

rehash

時使用。

rehashidx

記錄了

rehash

的進度,如果目前沒有進行的

rehash

,那麼它的值為

-1

下圖為一個普通狀態下(沒有進行rehash)的字典:

深入理解Redis 資料結構—字典

雜湊演算法

當要將一個新的鍵值對新增到字典中,程式需要先根據鍵值對中的鍵計算出雜湊值和索引值,然後根據索引值,將包含新鍵值的雜湊表放在雜湊表陣列的指定索引上。

Redis

計算雜湊值和索引值的步驟如下:

使用字典設定的雜湊函式,計算鍵的雜湊值。

hash = dict—>type->hashFunction(key)

使用雜湊表的

sizemask

屬性和雜湊值,取餘計算出雜湊值。

index = hash & dict ->ht[x].sizemask

瞭解過HashMap底層原理的同學應該知道,上面計算索引值和HashMap找到索引下標的原理是類似的。

什麼是取餘

&

運算?

取餘就是計算兩數相除的餘數, 比如一個數組長度為4,索引範圍是

0~3

,需要放置

0,1,7

,放置如下圖所示:

深入理解Redis 資料結構—字典

舉個例子,要將一個鍵值對

k0

v0

新增到下方的空字典表中:

深入理解Redis 資料結構—字典

首先計算鍵的雜湊值:

hash = dict—>type->hashFunction(key)

計算鍵

k0

的雜湊值。

假設計算出來的雜湊值為

8

,然後計算索引值:

index = dict -> hash & ht[0]。sizemask = 8 & 3 = 0;

計算出鍵

k0

的索引值

0

,這表示鍵值對

k0

v0

的節點放置到雜湊表陣列下標

0

的位置上,如下圖所示:

深入理解Redis 資料結構—字典

鍵衝突

當兩個或者兩個以上的計算出陣列索引值一致時,就發生了

鍵衝突

Redis的雜湊表採用

連結串列法

來解決鍵衝突,每個雜湊表的節點都有一個

next

指標,多個雜湊表節點用

next

指標組成一個單鏈表,被分配到同一個陣列索引上的多個節點使用單向連結串列連線起來,這就很好的解決了

鍵衝突

的問題。

舉個例子,程式要將一個鍵值對

k2

v2

新增到下圖的雜湊表中,並且計算

k2

的索引值為

2

,那麼鍵

k1

k2

將發生衝突:

深入理解Redis 資料結構—字典

解決衝突的辦法

就是使用

next

指標將

k2

k1

所在的節點連線起來,如下圖所示:

深入理解Redis 資料結構—字典

總結

字典是一種對映的

鍵值對

資料結構,其中鍵是唯一的,透過唯一的鍵可以快速找到對應的值。

字典包含廣泛用在

Redis

資料庫中。

其中所有資料型別的鍵值對都使用字典作為底層實現。

Hash

型別的鍵值對也是基於字典實現。

字典的結構

包含一個字典,包含根據特定型別處理的函式

dictType

、兩個雜湊表

ht[2]

,字典只用到了

ht[0]

,遇到了擴容才會使用

ht[1]

一個字典包含

兩個雜湊表

,每個雜湊表

dictht

包含一個

table

陣列,

size

記錄陣列的大小,

sizemask

等於

size-1

sizemask

和雜湊值決定資料存在在

table

的位置。

used

記錄已有的鍵值對。

雜湊表節點dictEntry

結構儲存一個鍵值對,其中

key

儲存鍵,

V

儲存值,

V

可以是一個指標、可以是

uint64_t

整數、也可以是

int64_t

的整數。

next

是為了解決

鍵hash衝突

,兩個鍵的計算出的索引在陣列的同一個地方,就使用

next`指標連線在一起。

新增一個鍵值對,首先透過呼叫

dict—>type->hashFunction(key)

計算

鍵的雜湊值

,再和

dictht

sizemask

做取餘操作,計算得到要存放

table

陣列的索引位置。如果發生

鍵衝突

時,使用

連結串列法

將多個雜湊節點透過

next

指標組成一個單鏈表。

Redis

字典的實現和

Java

中的

HashMap

資料結構有以下類似的點:

確定索引位置:

鍵首先使用雜湊演算法算出雜湊值,再和陣列的

長度-1

做取餘操作,確定存放陣列的下標。

解決hash衝突:

兩個鍵值計算的索引一致,採用

連結串列法

,將多個節點透過

next

指標連在一起。

參考

Redis設計與實現

Top