日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

开发 数组里面的字典_Redis字典结构与rehash解读

發(fā)布時(shí)間:2025/3/12 数据库 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 开发 数组里面的字典_Redis字典结构与rehash解读 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

關(guān)注公眾號(hào):后端技術(shù)漫談,技術(shù)之路不迷路~

字典是一種用于保存鍵值對(duì)的抽象數(shù)據(jù)結(jié)構(gòu),也被稱為查找表、映射或關(guān)聯(lián)表。

在字典中,一個(gè)鍵(key)可以和一個(gè)值(value)進(jìn)行關(guān)聯(lián),這些關(guān)聯(lián)的鍵和值就稱之為鍵值對(duì)。

抽象數(shù)據(jù)結(jié)構(gòu),啥意思?就是可以需要實(shí)際的數(shù)據(jù)結(jié)構(gòu)是實(shí)現(xiàn)這個(gè)功能。抽象,意味著它這是實(shí)現(xiàn)功能的標(biāo)準(zhǔn),凡是能夠完成這些功能的都可以是其實(shí)現(xiàn)。

redis的字典

字典作為一種數(shù)據(jù)結(jié)構(gòu)內(nèi)置在很多高級(jí)編程語言里面,但是redis是基于C語言進(jìn)行開發(fā)的,所以沒有內(nèi)置這種數(shù)據(jù)結(jié)構(gòu),redis只能構(gòu)建自己的字典實(shí)現(xiàn)。

字典通常可以由兩種底層數(shù)據(jù)結(jié)構(gòu)組成,分別是線性表(數(shù)組)和hash表。而redis一般是采用hash表的方式進(jìn)行構(gòu)建

redis字典為啥不用線性表實(shí)現(xiàn)

字典基于用線性表實(shí)現(xiàn),如果我這個(gè)字典有200個(gè)鍵值對(duì),那么我就開辟一個(gè)長度為200的數(shù)組對(duì)這些元素進(jìn)行放置。

基于線性表實(shí)現(xiàn)的字典的優(yōu)缺點(diǎn)很明顯:

1、實(shí)現(xiàn)簡單,適用于任意關(guān)鍵碼類型。

2、平均檢索效率低(線性時(shí)間),表長度n比較大時(shí),檢索比較耗時(shí)。

3、刪除操作的效率比較低,不太適合頻繁變動(dòng)的字典。

字典在插入刪除上的頻繁讓線性表無法勝任此任務(wù)。

哈希如何實(shí)現(xiàn)字典

之前寫過一篇文章,關(guān)于java中的hashcode解析,有興趣的讀者可以回看下一些經(jīng)典的hash函數(shù)和實(shí)現(xiàn)

面試官問我:hashcode 是什么?和equals是兄弟嗎?

redis字典所使用的哈希表由dict.h/dictht組成

typedef?struct?dictht?{
????dictEntry?**table;????//哈希表數(shù)組
????
????unsigned?long?size;????//哈希表大小,即哈希表數(shù)組大小
????
????unsigned?long?sizemask;?//哈希表大小掩碼,總是等于size-1,主要用于計(jì)算索引
????
????unsigned?long?used;????//已使用節(jié)點(diǎn)數(shù),即已使用鍵值對(duì)數(shù)
}dictht;

可以看到redis聲明了一個(gè)結(jié)構(gòu)體,里面由一個(gè)哈希表數(shù)組,哈希表數(shù)組大小的long值,一個(gè)用于計(jì)算索引的哈希表大小掩碼以及已使用的節(jié)點(diǎn)數(shù)構(gòu)成。

這個(gè)哈希表數(shù)組,存放的是哈希節(jié)點(diǎn)dicEntry,我們會(huì)將key-value鍵值對(duì)給它放進(jìn)去。

typedef?struct?dictEntry?{
????void?*key;??//存放key值
????union?{
????????void?*val;????//存放value值
????????uint64_t?u64;????//uint64_t整數(shù)
????????int64_t?s64;????//int64_t整數(shù)
????}v;
????struct?dictEntry?*next;????//指向下個(gè)哈希表節(jié)點(diǎn),形成鏈表
}dictEntry;

如圖所示就通過next指針來將兩個(gè)索引相同的鍵k1和k0連接在一起。

Redis 中的字典由 dict.h/dict 結(jié)構(gòu)表示:

typedef?struct?dict?{

????//?類型特定函數(shù)
????dictType?*type;

????//?私有數(shù)據(jù)
????void?*privdata;

????//?哈希表
????dictht?ht[2];

????//?rehash?索引
????//?當(dāng)?rehash?不在進(jìn)行時(shí),值為?-1
????int?rehashidx;?/*?rehashing?not?in?progress?if?rehashidx?==?-1?*/

}?dict;

可以看到字典里有一個(gè)長度為2的哈希表數(shù)組,那么為啥不是三個(gè)四個(gè)甚至更多呢?感覺哈希表越多不是效率更快嗎?

其實(shí)設(shè)置2的原因在于,h[0]用于存儲(chǔ),h[1]用于當(dāng)容量不足時(shí)進(jìn)行擴(kuò)充,更多的哈希表也用不上,反而可能在擴(kuò)充時(shí)要同步成為性能瓶頸。

字典如何增添一個(gè)元素

當(dāng)要將一個(gè)新的鍵值對(duì)加入到字典中的時(shí)候,首先要計(jì)算這個(gè)key的哈希值和索引值,然后再根據(jù)這個(gè)索引值放入字典中h[0]的索引位置

舉個(gè)例子, 對(duì)于圖 4-4 所示的字典來說, 如果我們要將一個(gè)鍵值對(duì) k0 和 v0 添加到字典里面, 那么程序會(huì)先使用語句:

hash?=?dict->type->hashFunction(k0);

計(jì)算鍵 k0 的哈希值。

假設(shè)計(jì)算得出的哈希值為 8 , 那么程序會(huì)繼續(xù)使用語句:

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

計(jì)算出鍵 k0 的索引值 0 , 這表示包含鍵值對(duì) k0 和 v0 的節(jié)點(diǎn)應(yīng)該被放置到哈希表數(shù)組的索引 0 位置上, 如圖 所示。

什么時(shí)候會(huì)進(jìn)行擴(kuò)容

按照java中hashmap的說法,當(dāng)負(fù)載因子loadFactor>0.75的情況下會(huì)進(jìn)行擴(kuò)容

在redis中,字典里的哈希會(huì)根據(jù)以下兩種情況進(jìn)行擴(kuò)容:

  • 服務(wù)器目前沒有在執(zhí)行 BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的負(fù)載因子大于等于 1 ;
  • 服務(wù)器目前正在執(zhí)行 BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的負(fù)載因子大于等于 5 ;
  • 其中哈希表的負(fù)載因子可以通過公式:

    #?負(fù)載因子?=?哈希表已保存節(jié)點(diǎn)數(shù)量?/?哈希表大小
    load_factor?=?ht[0].used?/?ht[0].size

    漸進(jìn)式rehash如何實(shí)現(xiàn)

    首先要清楚為什么rehash的時(shí)候要漸進(jìn)式。

    這就好比去參加高考,肯定是初中畢業(yè)后讀三年高中,一點(diǎn)點(diǎn)學(xué)習(xí)高中知識(shí)后才可以參加高考,這才可以取得不錯(cuò)的成績。學(xué)習(xí)是循序漸進(jìn)的,hash也要,不然中考完直接去參加高考,這誰頂?shù)米“ ?/p>

    Rehash操作分為兩種:

    擴(kuò)展:當(dāng)負(fù)載因子較大時(shí),應(yīng)該擴(kuò)大 dictht::size 以降低平均長度,加快查詢速度。
    收縮:當(dāng)負(fù)載因子較小時(shí),應(yīng)該減小 dictht::size 以減少對(duì)內(nèi)存的浪費(fèi)。

    當(dāng)整體的數(shù)據(jù)量比較少,如百八十個(gè)key-value對(duì)存儲(chǔ)的時(shí)候,hash的過程肯定耗時(shí)不會(huì)很多。但是在生產(chǎn)換鏡下,一個(gè)數(shù)據(jù)庫下key-value值都是有百萬級(jí)別的,在進(jìn)行rehash操作的時(shí)候勢必會(huì)達(dá)到秒級(jí)別的運(yùn)算。所以這個(gè)hash的過程不是一次性集中的完成,而是分多次漸進(jìn)式的完成。

    以下是哈希表漸進(jìn)式 rehash 的詳細(xì)步驟:

  • 為 ht[1] 分配空間, 讓字典同時(shí)持有 ht[0] 和 ht[1] 兩個(gè)哈希表。
  • 在字典中維持一個(gè)索引計(jì)數(shù)器變量 rehashidx , 并將它的值設(shè)置為 0 , 表示 rehash 工作正式開始。
  • 在 rehash 進(jìn)行期間, 每次對(duì)字典執(zhí)行添加、刪除、查找或者更新操作時(shí), 程序除了執(zhí)行指定的操作以外, 還會(huì)順帶將 ht[0] 哈希表在 rehashidx 索引上的所有鍵值對(duì) rehash 到 ht[1] , 當(dāng) rehash 工作完成之后, 程序?qū)?rehashidx 屬性的值增1。
  • 隨著字典操作的不斷執(zhí)行, 最終在某個(gè)時(shí)間點(diǎn)上, ht[0] 的所有鍵值對(duì)都會(huì)被 rehash 至 ht[1] , 這時(shí)程序?qū)?rehashidx 屬性的值設(shè)為 -1 , 表示 rehash 操作已完成。
  • rehash的過程中有數(shù)據(jù)變化怎么辦

    關(guān)于字典的操作無非就是四個(gè),增刪改查。

    操作類型過程
    增加直接將key-value對(duì)增加到h[1]中
    刪除先刪除h[0],再刪除h[1]
    修改直接修改h[1]
    查找先在h[0]中查找,查詢不到再到h[1]中

    這樣就能保證redis在h[0]上是只少不多,所有的記錄都會(huì)被遷移到h[1]上。

    如何解決哈希碰撞

    這個(gè)問題還是我面試騰訊的時(shí)候面試官問我的原題。

    一開始我說了兩個(gè)思路,一個(gè)是無限增大線性表的容量,一個(gè)是采用數(shù)組+鏈表的方式。

    面試官:對(duì)這兩個(gè)都是構(gòu)成hash的方式,但是如果我的兩個(gè)鍵值對(duì)的hashcode是一樣的呢?

    我:那就可以將這個(gè)hash算法設(shè)計(jì)的復(fù)雜化,比如hash里頭再嵌套一層hash,這樣碰撞的幾率就會(huì)變小了。

    面試官:這種方法其實(shí)也是可以的,那還有沒有其他方法呢?

    我:....(支支吾吾中)

    然后面試就結(jié)束了orz

    其實(shí)還有另一種方法我不知道就是公共溢出區(qū)法

    建立一個(gè)公共溢出區(qū),假設(shè)哈希函數(shù)的值域?yàn)閇0,m-1],則設(shè)向量HashTable[0..m-1]為基本表,另外設(shè)立存儲(chǔ)空間向量OverTable[0..v]用以存儲(chǔ)發(fā)生沖突的記錄。

    參考文獻(xiàn):
    《redis設(shè)計(jì)與實(shí)現(xiàn)》
    https://blog.csdn.net/Time_Limit/article/details/106633269

    往期精彩文章:

    闊別2020 ?| ?我的年度總結(jié)

    Socket粘包問題的3種解決方案,最后一種最完美!

    一條失去where的動(dòng)態(tài)SQL導(dǎo)致的線上故障

    一枚程序猿的MacBook M1使用體驗(yàn)

    半夜里,有程序從虛擬機(jī)里跑出來了!

    總結(jié)

    以上是生活随笔為你收集整理的开发 数组里面的字典_Redis字典结构与rehash解读的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。