欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis底層數(shù)據(jù)結(jié)構(gòu)之字典(Dict)的實(shí)現(xiàn)

 更新時(shí)間:2025年06月06日 08:54:35   作者:碼農(nóng)開荒路  
本文主要介紹了Redis底層數(shù)據(jù)結(jié)構(gòu)之字典(Dict)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

Dict基本結(jié)構(gòu)

Dict我們可以想象成目錄,要翻看什么內(nèi)容,直接通過目錄能找到頁(yè)數(shù),翻過去看。如果沒有目錄,我們需要一頁(yè)一頁(yè)往后翻,這樣時(shí)間復(fù)雜度就與遍歷的O(n)一樣了,而用了Dict我們就可以在O(1)的時(shí)間復(fù)雜度內(nèi)快速找到鍵對(duì)應(yīng)的值。說(shuō)到這里,大家會(huì)覺得Dict與JAVA中的哈希表功能差不多,其實(shí),Redis的Dict數(shù)據(jù)結(jié)構(gòu)底層實(shí)現(xiàn)正是哈希表,不過維護(hù)了2個(gè)哈希表。Redis實(shí)現(xiàn)Dict數(shù)據(jù)結(jié)構(gòu)創(chuàng)建了三個(gè)重要的結(jié)構(gòu)體,分別是dict、dictht和dictEntry。下面先給出Dict的整體結(jié)構(gòu)幫助大家更好的理解一下:

dict

typedef struct dict{
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx;
    unsigned long iterators;
} dict;
  • ht[2]:表示在一個(gè)Dict結(jié)構(gòu)中,包含有兩個(gè)dictht的結(jié)構(gòu),也就是我們說(shuō)的兩張哈希表。
  • rehashidx:是dict在rehash時(shí)的偏移索引,具體如何工作在后邊的rehash過程中會(huì)詳細(xì)講。

dictht

typedef struct dictht{
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
}dictht
  • table:指向?qū)嶋Hhash存儲(chǔ)。存儲(chǔ)可以看做是一個(gè)數(shù)組,所以是*table表示。(源碼中的**table是一個(gè)二級(jí)指針,也就是指向dictEntry*的指針)。
  • size:哈希表的大小。實(shí)際就是dictEntry有多少元素空間。
  • sizemask:哈希表大小的掩碼表示,總是等于size-1.這個(gè)屬性和哈希值一起決定一個(gè)鍵應(yīng)該被放到table數(shù)組的哪個(gè)索引上面,索引計(jì)算規(guī)則是index=hash&sizemask,前提是size的大小是二次方冪,這一點(diǎn)與JAVA哈希表底層計(jì)算索引是一樣的原理。
  • used:表示已經(jīng)使用的節(jié)點(diǎn)數(shù)量。通過這個(gè)字段可以很方便地查詢到目前dict元素總量。

dictEntry

typedef struct dictEntry{
     void *key;
     union{
          void *val;
          uint64_tu64;
          int64_ts64;
     }v;
     struct dictEntry *next;
}dictEntry
  • *key:存儲(chǔ)鍵。
  • v:用來(lái)存儲(chǔ)具體的值,可以看到,值可以是一個(gè)指針,可以是uint64_t整數(shù),也可以是int64_t整數(shù)。
  • *next:用于采用拉鏈法將相同索引的dictEntry串起來(lái),解決哈希沖突問題。(采用的是頭插法,JAVA中JDK8之后采用的是尾插法,留個(gè)小問題,為什么JAVA中不延續(xù)使用頭插法?)

Dict的漸進(jìn)式擴(kuò)容機(jī)制

想必大家有一個(gè)疑問,為什么Dict底層要維護(hù)兩張哈希表,實(shí)際存儲(chǔ)的話使用一張哈希表不就可以了嗎。其實(shí),第二張哈希表的存在是為了給第一張哈希表的擴(kuò)容提供支持。下面我們來(lái)詳細(xì)介紹一下Dict中哈希表的漸進(jìn)式擴(kuò)容流程和擴(kuò)容時(shí)機(jī)。

Dict漸進(jìn)式擴(kuò)容流程

首先,當(dāng)向字典添加新元素時(shí),發(fā)現(xiàn)第一張哈希表ht[0]需要擴(kuò)容,就會(huì)進(jìn)行rehash操作,為第二張哈希表ht[1]分配空間。ht[1]表的大小為大于等于ht[0]表used值的2倍的2次方冪。舉個(gè)例子,如果ht[0]中已經(jīng)使用的節(jié)點(diǎn)數(shù)量為500,那么擴(kuò)容時(shí)ht[1]被分配的空間是1024而不是1000。這么做是為了維護(hù)擴(kuò)容后表的大小始終是2次方冪。

接著,dict的rehashidx由靜默狀態(tài)(-1)變?yōu)殚_始工作狀態(tài)(0)。

最后,遷移ht[0]中的數(shù)據(jù)到ht[1],也就是將數(shù)據(jù)從舊表中遷移到新表中。在rehash進(jìn)行期間,每次對(duì)dict執(zhí)行增刪改查操作,程序會(huì)順帶遷移當(dāng)前rehashidx在ht[0]上對(duì)應(yīng)的數(shù)據(jù),并更新偏移索引。與此同時(shí),部分情況周期函數(shù)也會(huì)進(jìn)行遷移。如果rehashidx剛好在一個(gè)已刪除的空位置上,那么是直接返回還是嘗試往下找?我們來(lái)看一下dictRehash函數(shù)的源碼:

//int empty_visits = n*10;//Max number of empty buckets to visit.

while(d->ht[0].table[d->rehashidx] == NULL) {
    d->rehashidx++;
    if (--empty_visits == 0) return 1;
}

可以看到,答案是會(huì)繼續(xù)往下去找,但是有個(gè)上限是n*10,即最多再找這么多次,n是傳進(jìn)來(lái)的參數(shù),調(diào)用的時(shí)候?qū)嶋H值為1,即最多往后再找10個(gè),這么做是防止因?yàn)檫B續(xù)碰到空位置導(dǎo)致主線程操作被阻塞。

隨著字典操作的不斷執(zhí)行,最終在某個(gè)時(shí)間點(diǎn)上,ht[0]的所有鍵值對(duì)都會(huì)被rehash到ht[1],此時(shí)再將ht[1]和ht[0]指針對(duì)象互換,同時(shí)將rehashidx設(shè)置為-1,表示rehash工作已經(jīng)完成。這個(gè)事情也是在rehash函數(shù)做的,每次遷移完一個(gè)元素,會(huì)檢查是否已經(jīng)完成了整個(gè)遷移:

if (d->ht[0].used == 0) {
    zfree(d->ht[0].table);
    d->ht[0] = d->ht[1];
    _dictReset(&d->ht[1]);
    d->rehashidx = -1;
    return 0;
}

總結(jié)一下,漸進(jìn)式擴(kuò)容的核心就是刪改查操作時(shí)順帶遷移,其中增的操作直接增到新表中。

Dict漸進(jìn)式擴(kuò)容時(shí)機(jī)

Redis提出了一個(gè)負(fù)載因子的概念(與JAVA中的負(fù)載因子不同),用于表示目前Dict的使用情況,是情況良好還是已經(jīng)堵塞不堪。設(shè)負(fù)載椅子為F,那么負(fù)載因子計(jì)算公式為F=ht[0].used/ht[0].size,也就是使用空間大小和總空間大小的比值。Redis會(huì)根據(jù)負(fù)載因子的情況來(lái)進(jìn)行擴(kuò)容。

當(dāng)負(fù)載因子的值小于1時(shí),認(rèn)為dict的使用情況良好,不需要進(jìn)行擴(kuò)容。

當(dāng)負(fù)載因子的值大于等于1時(shí),說(shuō)明此時(shí)的dict空間已經(jīng)非常緊張了,新增的數(shù)據(jù)會(huì)發(fā)生哈希沖突在鏈表上堆疊。如果此時(shí)服務(wù)器沒有執(zhí)行BGSAVE或者BGREWRITEAOF這兩個(gè)復(fù)制命令,就會(huì)立刻進(jìn)行擴(kuò)容,反之則不會(huì)立刻擴(kuò)容。

當(dāng)負(fù)載因子的值大于5時(shí),說(shuō)明此時(shí)的dict中哈希沖突已經(jīng)非常嚴(yán)重了,哈希表的搜索性能嚴(yán)重退化向鏈表。此時(shí)不管服務(wù)器是否在執(zhí)行復(fù)制命令,都會(huì)立刻對(duì)哈希表進(jìn)行擴(kuò)容操作。

Dict為什么采用漸進(jìn)式擴(kuò)容機(jī)制?

在JAVA中,哈希表其實(shí)也有擴(kuò)容操作,并且是在單張表上完成的rehash操作。但是對(duì)于Redis中的Dict來(lái)說(shuō),兩者存放的數(shù)據(jù)量不在一個(gè)量級(jí)上,由于Redis是單線程的,如果對(duì)dict中存放的大量數(shù)據(jù)進(jìn)行一次性rehash,那么耗費(fèi)的時(shí)間會(huì)非常久,從而造成主線程的長(zhǎng)時(shí)間阻塞。為了性能考慮,Dict采用空間換時(shí)間的方法,多花費(fèi)一張表的空間,配合漸進(jìn)式擴(kuò)容機(jī)制,幾乎完全消除rehash可能造成的主線程阻塞。

Dict的漸進(jìn)式縮容機(jī)制

擴(kuò)容是數(shù)據(jù)太多裝不下,那么對(duì)應(yīng)的縮容就是空間太富裕造成了浪費(fèi)??s容的過程其實(shí)和擴(kuò)容是相似的,也是漸進(jìn)式縮容,這里就不詳細(xì)展開了。

同樣的,Redis也通過負(fù)載因子來(lái)控制什么時(shí)候縮容:

當(dāng)負(fù)載因子大于等于0.1時(shí),認(rèn)為dict的空間合適,不需要進(jìn)行縮容。

當(dāng)負(fù)載因子小于0.1時(shí),認(rèn)為dict的空間太大造成浪費(fèi),進(jìn)行縮容。ht[1]的大小為第一個(gè)大于等于ht[0]中used值的2次方冪(最小為4,如果已經(jīng)是4了那就保持不變)。

同樣的,如果有BGSAVE或者BGREWRITEAOF這兩個(gè)復(fù)制操作正在執(zhí)行,縮容也會(huì)受影響,不會(huì)進(jìn)行。

總結(jié)

Dict數(shù)據(jù)結(jié)構(gòu)提供了快速索引數(shù)據(jù)的能力,其結(jié)構(gòu)的設(shè)計(jì)和漸進(jìn)式擴(kuò)容的設(shè)計(jì)很值得大家學(xué)習(xí)。

到此這篇關(guān)于Redis底層數(shù)據(jù)結(jié)構(gòu)之字典(Dict)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Redis 字典內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 為啥懶 Redis 是更好的 Redis

    為啥懶 Redis 是更好的 Redis

    本文是由zicode, 李中凱, 無(wú)若翻譯的英文文章Lazy Redis is better Redis,小編認(rèn)為非常不錯(cuò),這里推薦給大家
    2018-07-07
  • Redis限流的幾種實(shí)現(xiàn)

    Redis限流的幾種實(shí)現(xiàn)

    面對(duì)越來(lái)越多的高并發(fā)場(chǎng)景,限流顯示的尤為重要,限流有許多種實(shí)現(xiàn)的方式,Redis具有很強(qiáng)大的功能,本文就詳細(xì)的介紹幾種方式,感興趣的可以了解一下
    2021-12-12
  • 淺談Redis緩存擊穿、緩存穿透、緩存雪崩的解決方案

    淺談Redis緩存擊穿、緩存穿透、緩存雪崩的解決方案

    這篇文章主要介紹了淺談Redis緩存擊穿、緩存穿透、緩存雪崩的解決方案,緩存是分布式系統(tǒng)中的重要組件,主要解決在高并發(fā)、大數(shù)據(jù)場(chǎng)景下,熱點(diǎn)數(shù)據(jù)訪問的性能問題,需要的朋友可以參考下
    2023-03-03
  • 詳解redis中的下載和安裝(最新推薦)

    詳解redis中的下載和安裝(最新推薦)

    本文詳細(xì)介紹了如何在Linux和Docker上安裝、配置、啟動(dòng)和關(guān)閉Redis,包括下載安裝包、解壓、編譯安裝、配置文件修改、前臺(tái)和后臺(tái)啟動(dòng)、關(guān)閉以及Docker容器化部署等步驟,感興趣的朋友一起看看吧
    2025-03-03
  • Redis 內(nèi)存碎片原因及清理

    Redis 內(nèi)存碎片原因及清理

    內(nèi)存碎片是指在內(nèi)存分配的時(shí)候,產(chǎn)生的不能重復(fù)利用的空間,本文主要介紹了Redis 內(nèi)存碎片原因及清理,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-06-06
  • redis源碼分析教程之壓縮鏈表ziplist詳解

    redis源碼分析教程之壓縮鏈表ziplist詳解

    ziplist結(jié)構(gòu)在redis運(yùn)用非常廣泛,是列表、字典等數(shù)據(jù)類型的底層結(jié)構(gòu)之一。ziplist的優(yōu)點(diǎn)在于能夠一定程度地節(jié)約內(nèi)存。下面這篇文章主要給大家介紹了關(guān)于redis源碼分析教程之壓縮鏈表ziplist的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-12-12
  • Redis動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)緩存策略設(shè)計(jì)

    Redis動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)緩存策略設(shè)計(jì)

    本文主要介紹了Redis動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)緩存策略設(shè)計(jì),包括熱點(diǎn)數(shù)據(jù)識(shí)別、動(dòng)態(tài)緩存、多級(jí)緩存、預(yù)加載機(jī)制、更新策略以及監(jiān)控告警等,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-01-01
  • 詳解三分鐘快速搭建分布式高可用的Redis集群

    詳解三分鐘快速搭建分布式高可用的Redis集群

    這篇文章主要介紹了詳解三分鐘快速搭建分布式高可用的Redis集群,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • Redis常用數(shù)據(jù)類型命令實(shí)例匯總

    Redis常用數(shù)據(jù)類型命令實(shí)例匯總

    這篇文章主要介紹了Redis常用數(shù)據(jù)類型命令實(shí)例匯總,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Redis如何設(shè)置過期時(shí)間

    Redis如何設(shè)置過期時(shí)間

    這篇文章主要介紹了Redis如何設(shè)置過期時(shí)間問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04

最新評(píng)論