Redis資料結構之鍊錶與字典的使用

2022-09-21 01:24:10 字數 3306 閱讀 4574

今天我們來聊一聊redis中的鍊錶與字典,具體如下:

因為redis是使用c語言編寫的,因此redis的資料結構的定義都是使用c語法定義的,你不需要完全理解下方c語言宣告結構體的語法,但我認為依靠大家的j**a知識也能理解這就像是在j**a中定義了乙個鍊錶物件

redis鍊錶節點的結構

typedef struct listnode listnode;

很明顯,當每乙個節點內記錄了前後兩個節點位置之後,鍊錶節點之間就能夠彼此前後相連,組成雙向通行車道(可以雙向遍歷)

上面講解了redis的鍊錶的節點程式設計客棧表示,並由此引申了一下可以藉此構建redis雙端鍊錶,而事實上,對於每乙個存在的雙端鍊錶,redis使用乙個list結構來表示

typedef struct list list;

很明顯,你看到三個好像是返回值為void的函式,但是看不懂c語法,沒關係,傳統後端功夫,自然是點到為止

我不想現在就告訴你,鍊錶被廣泛用於實現redis的各種功能,比如列表鍵、發布於訂閱、慢查詢、監視器等,等我們後面講到這幾部分的時候,白澤再結合鍊錶和你細說~

和鍊錶一樣,redis所使用的c語言並沒有內建字典這種資料結構,因此redis構建了自己的字典實現。如果你學過資料結構,你會發現redis的字典事實上就是資料結構中的鄰接表,即使沒學過,往下看就好啦~

陣列 + 鍊錶 ==> 鄰接表,實錘

還記得嗎,上面我們說redis鍊錶可以用list描述,但是鍊錶儲存的資料本質上,是由一系列listnode節點通過前後指標相連儲存的;類似的,redis字典可以用如下dict描述,但是字典儲存的資料本質上,是由陣列 + 若干鍊錶組合得到的資料結構儲存的,字典dict結構如下:

typedef struct dict dict;

現在你只需要關注其中的雜湊表陣列ht[2],它的資料型別為dictht,因此也是一種復合的資料結構,如下:

typedef struct dictht dictht;

雜湊表dictht是redis字典的核心,dictht的四個屬性中,size、sizemax、used都是用於描述table屬性整體狀態。看到這你就明白了,dictht的核心是dictentry型別的table屬性(再次提醒,如果沒有c語言的基礎,本文中一切你看不懂的語法,包括資料型別,你只需要一眼帶過即可,我們的目的是學習redis的設計思想)

table屬性是乙個陣列,陣列中的每個元素都是乙個指向dictentry結構的指標,每個dictentry結構儲存乙個鍵值對,並含有乙個指向下乙個dictentry的指標,結構如下:

typedef struct dictentry v;

struct dictentry *next;//指向下個雜湊表節點,形成鍊錶

} dictentry;

我們知道,字典是用來儲存資料的,並且是以鍵值對的形式儲存的,那麼我每次存入乙個鍵值對放在字典的**?這就是雜湊演算法為你解決的事情:程式需要先根據鍵值對的鍵計算出雜湊值和索引值,然後再根據索引值,將包含新鍵值對的雜湊表節點放到雜湊表陣列的指定索引上面

比如我已經有下面這個字典,然後要插入乙個鍵值對資料:k1 : v1,則程式有如下計算過程:(使用者只是往redis伺服器中插入了一條資料,下面都是程式內部的工作~)

hash = dict->type->hashfunction(k1); //計算k1鍵的hash值(得到某個數值)

index = hash & dict->ht[0].sizemask = 1; //計算k1鍵插入位置的程式設計客棧索引值

解決鍵衝突

鍵衝突:當不同的key值計算得到的dictentry索引www.cppcns.com值相同時,就稱發生鍵衝突(我要插入的位置已經被占用了,插入使得鍊錶長度由1變多,當然第一次插入不算衝突)

解決方法:

就像上面我要插入乙個k1 :v1的鍵值對,並計算得到插入位置的索引為1(但是distentry陣列中索引為1的位置已經有k0 :v0鍵值對存放了),因此程式會在雜湊表ht[0]的dictentry陣列的索引為1的位置上插入乙個dictentry節點,放在原本鍊錶首部的前一位置(搶占首位),其中存放著k1 : v1鍵值對,插入後的圖如下:

你可能疑惑新插入的鍵值對的位置在每個dictentry鍊錶的最前面,而不是尾部,原因是每個dictentry中除了儲存鍵值對之外,只記錄了下乙個dictentry的位址(上面我已經給出了dictentry的結構了~),程式無法直接得到dictentry鍊錶的最後乙個節點,但可以直接得到第乙個節點(通過dictentry陣列索引直接定位),因此每次插入的dictentry節點(鍵值對)都將直接插入到對應索引的鍊錶的頭部(因此dictentry陣列的內容是不斷在變的)

一句話來說:distentry陣列幫助使用索引定位,distentry鍊錶,用於處理衝突,不斷維護所儲存的鍵值對資料

隨著操作的不斷執行(增、刪、改、查),雜湊表儲存的鍵值對會逐漸增多或者減少,為了讓雜湊表的負載因子維持在乙個合理範圍內,當雜湊表儲存的鍵值對數量太多或太少時,程式會對雜湊表的大小進行相應的擴充套件或者收縮(不知道你是否記得還有乙個雜湊表ht[1]的存在,這個表就是為了和ht[0]配合進行rehash而存在的)

rehash步驟:

為字典的ht[1]雜湊表分配空間

如果程式執行擴充套件操作:

ht[1].size = 第乙個大於等於ht[0].used * 2(ht[0]已經使用的空間大小乘2)的2的n次方冪

如果程式執行收縮操作:

ht[1].size = 第乙個大於等於ht[0].used(ht[0]已經使用的空間大小)的2的n次方冪

將儲存在ht[0]上的鍵值對rehash到ht[1]上,因為size不同,所以是重新hash,而不是整體複製

當ht[0]內鍵值對全部遷移到ht[1]中後,釋放ht[0],然後將ht[1]和ht[0]的互換(rehash結束),此時ht[0]就是乙個rehash後的雜湊表,而ht[1]依舊為空表,為下次rehash做準備

上面提到的在雜湊表ht[0]的負載因子過大或者過小會觸發rehash,但是,事實上rehash遷移的過程不是一蹴而就的(很明顯,如果資料ht[0]的資料很多,每次rehash如果都遷移全部資料,需要花費較大時間等待,使用者在rehash期間訪問redis伺服器將會陷入無響應的狀態)

漸進式過程:

將rehash的過程分攤在後續的每次增、刪、改、查操作上,在rehash期間,每次對字典執行操作,程式除了執行指定操作外,還會順帶將ht[0]雜湊表在rehashidx索引(從0開始,-1表示rehash未開始)上的所有鍵值對rehash到ht[1],當每次區域性rehash工作完成後,程式將rehashidx屬性的值增一

注意:每次對字典進行增、刪、改、查會在ht[0]和ht[1]上同時進行,比如查詢乙個鍵,則會現在ht[0]上查詢,沒找到再去ht[1]上查詢,諸如此類,除了增加操作每次都將直接hash到ht[1]上,不會對ht[0]執行任何新增操作

Redis資料結構之鍊錶

鍊錶基礎知識 鍊錶是一種物理儲存單元上非連續 非順序的儲存結構,資料元素的邏輯順序通過鍊錶中的指標鏈結次序來實現。其內部是由一系列的資料節點組成,資料節點可以動態分配。鍊錶與陣列的比較 陣列可以隨機訪問,鍊錶只能順序訪問。鍊錶容量可以動態擴充陣列不可以,鍊錶新增資料元素,不需要資料的移動。陣列新增元...

Redis資料結構之鍊錶

redis使用的鍊錶是雙向無環鏈表,鍊錶節點可用於儲存各種不同型別的值。一 鍊錶結構定義 1.鍊錶節點結構定義 2.鍊錶結構定義 示例 二 鍊錶在redis中的用途 1.作為列表鍵的底層實現之一 當乙個列表鍵包含了數量比較多的元素,又或者列表中包含的元素都是比較長的字串時,redis就會使用鍊錶作為...

redis資料結構之字典

字典 dictionary 其實和符號表 symbol table 關聯陣列 associative array 對映 map 是乙個東東,都是為了儲存鍵值對 k v pair 的資料結構,屌屌噠。php中因為能很方便的用關聯陣列,因此能寫出很多實用高效的 有空一定要去好好了解下php的關聯陣列是如...