redis中字典的add操作(hash演算法、rehash)
下面先來看看dict中的dictAdd方法:
/* * 三個引數:字典指標、鍵、值 */ int dictAdd(dict *d, void *key, void *val) { /* 新建節點,entry=null */ dictEntry *entry = dictAddRaw(d,key,NULL); /* 如果entry不為null,返回1 */ if (!entry) return DICT_ERR; /* 給節點賦值 */ dictSetVal(d, entry, val); /* 操作成功,返回0 */ return DICT_OK; }
主要看看dictAddRaw:
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing) { long index; dictEntry *entry; dictht *ht; /* 指向字典中的hash表 */ /* 判斷字典此時是否正在rehash */ if (dictIsRehashing(d)) _dictRehashStep(d); /* 如果新元素已經存在,那麼index=-1,否則index就是新元素的下標值 */ if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1) return NULL; /* 給新的entry分配記憶體空間並且儲存新的entry, * 在這裡,會將新的元素放在hash表的表頭 */ /* 如果字典這是正在rehash,那麼會將entry新增到ht[1]中去;否則新增到ht[0] */ ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0]; entry = zmalloc(sizeof(*entry)); entry->next = ht->table[index]; ht->table[index] = entry; ht->used++;/* 更新hash表中used屬性的值 */ /* 設定entry的key */ dictSetKey(d, entry, key); return entry; }
判斷字典是否正在rehash是根據dict中的rehashidx屬性來判斷,如果rehashidx=-1,那麼dict沒有在rehash,否則dict在rehash。
#define dictIsRehashing(d) ((d)->rehashidx != -1)
rehash
如果此時rehashidx!=-1,並且迭代器數量=0,也就是此時並沒有對dict進行迭代,那麼就rehash。以下就是dict的rehash方法:
int dictRehash(dict *d, int n) { int empty_visits = n*10; /* 空桶的最大值 */ if (!dictIsRehashing(d)) return 0; /* 沒有在rehash,那麼返回0 */ /* 遍歷ht[0] */ while(n-- && d->ht[0].used != 0) { /* 倆指標,分別指向當前節點和當前節點的下一節點 */ dictEntry *de, *nextde; /* 判斷ht[0].size>rehashidx,確保不會溢位 */ assert(d->ht[0].size > (unsigned long)d->rehashidx); /* ht[0]已經被遍歷完,返回1 */ while(d->ht[0].table[d->rehashidx] == NULL) { d->rehashidx++; if (--empty_visits == 0) return 1; } /* 當前節點 */ de = d->ht[0].table[d->rehashidx]; /* 將所有節點從ht[0]移動到ht[1]中 */ while(de) { uint64_t h; nextde = de->next; /* 獲取該節點在ht[1]中的新的位置 */ h = dictHashKey(d, de->key) & d->ht[1].sizemask; de->next = d->ht[1].table[h]; d->ht[1].table[h] = de; d->ht[0].used--; d->ht[1].used++; de = nextde; } d->ht[0].table[d->rehashidx] = NULL; d->rehashidx++; } /* 如果ht[0]中的元素全部已經放入ht[1],那麼釋放ht[0]的空間 * 將ht[0]指向ht[1],重置ht[1],保證dict中總是使用ht[0],ht[1]作為備用 */ if (d->ht[0].used == 0) { zfree(d->ht[0].table); d->ht[0] = d->ht[1]; _dictReset(&d->ht[1]); /* rehash操作結束 */ d->rehashidx = -1; return 0; } return 1; }
在rehash中也涉及到對key的hash演算法:
/* 使用redis的hashFunction來計算得到key的hash值 */
#define dictHashKey(d, key) (d)->type->hashFunction(key)
最後會重置ht[1]:
static void _dictReset(dictht *ht)
{
ht->table = NULL;
ht->size = 0;
ht->sizemask = 0;
ht->used = 0;
}
回到addDictRaw方法主體,rehash之後,通過_dictKeyIndex方法獲取key的下標值:
static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
{
unsigned long idx, table;
dictEntry *he;
/* key已經存在,返回null */
if (existing) *existing = NULL;
/* 判斷是否需要擴充套件dict */
if (_dictExpandIfNeeded(d) == DICT_ERR)
return -1;
/* 遍歷dict中的兩個hash表 */
for (table = 0; table <= 1; table++) {
idx = hash & d->ht[table].sizemask;
/* 遍歷table中所有的已存在的key。判斷新增的key是否已經存在 */
he = d->ht[table].table[idx];
while(he) {
/* 已經存在,返回-1 */
if (key==he->key || dictCompareKeys(d, key, he->key)) {
if (existing) *existing = he;
return -1;
}
he = he->next;
}
if (!dictIsRehashing(d)) break;
}
/* 新增的key不存在,返回新增的key的下標值,也就是hash表的sizemask=size-1 */
return idx;
}
總結:通過閱讀add方法,瞭解了hash演算法和rehash操作。在rehash的時候,需要將ht[0]中的所有K-V移動到ht[1]中去,這個過程並不是一次性完成,而是“漸進的”。為什麼這樣做呢?這是因為如果ht[0]中的K-V很多,一次性的移動會對伺服器的效能有影響。在rehash過程中,如果執行查詢操作,那麼會先去ht[0]中查詢,如果沒有找到,再去ht[1]中查詢。rehash過程中,如果有新增操作,那麼會直接儲存到ht[1]中,確保ht[0]中的K-V數量只減不增。
另,在add過程中,如果兩個不同的key被分配到同一個hash表中的索引上時,會產生hash衝突,解決這個衝突的方法是鏈地址法,與java中hashmap的解決方法相似。
相關推薦
redis中字典的add操作(hash演算法、rehash)
下面先來看看dict中的dictAdd方法: /* * 三個引數:字典指標、鍵、值 */ int dictAdd(dict *d, void *key, void *val) { /* 新建節點,entry=null */ dictEntry *entr
python對字典的基本操作(遍歷、排序)總結
Python字典容器 python中的字典同其他語言中的字典作用一樣,都是用來儲存資料的容器。只不過不同於其他序列型資料用下標來訪問其中的物件,而是以關鍵字key來訪問其中的物件value。另外,字典也被稱為關聯陣列或者雜湊表。 字典的應用場景有很多,下面通過一個投票的例
Swift IOS中的常用操作(開啟網頁、發簡訊、打電話、發郵件)
// // ViewController.swift // Other // // Created by 顧傑 on 15/11/26. // Copyright © 2015年 GuJie. All rights reserved. // import UIKit <span style="
C++中常用小函式(刷演算法題必備)
int a = 123; string str1 = to_string(a);////將整數轉化為string型別 string str = "abcdefg"; //str.substr(1
Android開發:如何在選單中呼叫控制元件(如Button、TextView……)
當我們在類內定義控制元件的全域性變數時,如Button……,只能在onCreate()中初始化,這樣的控制元件變數引用在選單中不好引用,會報錯。 如果想在選單中呼叫控制元件,可以在選單中重新定義控制元件
在jupyter notebook中安裝第三方包(解除安裝、檢視)
在jupyter中安裝解除安裝檢視第三方包 import pip def pip_install(package): pip.main(['install', package]) def
鏈佇列的綜合操作(詳解、演示)C語言實現
佇列的簡介 和棧相反,佇列(queue)是一種先進先出(簡稱FIFO)的線性表。它只允許在表的一端進行插入,而在另一端刪除元素。 舉個我們生活中最最常見的例子:銀行排隊(不管什麼排隊),當我們去銀行辦理業務的時候,我們要按照先來後到的規矩,先來
redis原始碼分析與思考(三)——字典中鍵的兩種hash演算法
在Redis字典中,得到鍵的hash值顯得尤為重要,因為這個不僅關乎到是否字典能做到負載均衡,以及在效能上優勢是否突出,一個良好的hash演算法在此時就能發揮出巨大的作用。而一個良好的has
redis數據庫操作(3)
edi 添加元素 查詢 store 數據庫 無序 排名 IT 操作 set類型 添加元素到無序集合:sadd KEY VALUE1 VALUE2 VALUE3 例:( sadd my_set1 a b c d d c s a ) 獲取無序集合的元素:smembers K
JS中的BOM操作(一)
目錄 Tips 1.BOM簡介 2.對話方塊 3.載入事件 4.location物件 5.history物件 6.navigator物件 Tips 1.BOM簡介 JavaScript分三個部分:
JS中的DOM操作(四)
目錄 Tips 1. 繫結事件的區別 2.為元素解綁事件 3.事件冒泡 程式碼 1.為元素繫結事件和解綁事件的相容程式碼 2.為同一個元素繫結多個不同的事件,指向相同的事件處理函式 Tips 1. 繫結事件的區別 &n
JS中的DOM操作(三)
目錄 Tips 節點與元素: 節點的屬性: 獲取結節點的方法: 節點相容程式碼: 元素建立的三種方式 元素繫結多個事件 元素繫結事件的相容程式碼 程式碼 1.案例點選按鈕設定div中p標籤改變背景顏色 2.節點操作隔行變色 3.切換背
Django中的ORM操作(個人筆記)
一、ORM ORM:Object Relational Mapping(關係物件對映) 類名對應------》資料庫中的表名 類例項對應---------》資料庫表裡的一行資料 類屬性對應---------》資料庫裡的欄位 obj.id obj.nam
Hive Shell 命令之二(表中資料的操作,出自Hive程式設計指南)
一、 互動模式: show tables; #檢視所有表名 show tables 'ad*' #檢視以'ad'開頭的表名 set 命令 #設定變數與檢視變數; set -v #檢視所有的變數 set hive.stats.atomic #檢視hive.sta
Java中的HashCode 1 之hash演算法基本原理
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
串的基本操作(KMP演算法實現)
#include <iostream> #include <math.h> using namespace std; void StrAssign(char *T) { char ch; int i=1; cout<<"Please enter a str
python中list常用操作(不包括切片)
stus = ['abc‘,’dec',’dxq‘,’wzw‘] #下標,索引,角標 stus[3] stus = [] #空陣列 stus = list() #空列表 #增加元素 stus.append('zhaoyan') #在列表末尾增加一個元素 stus.inse
Python中字典的操作
存在 最好 是否 bag ems pri mon one 報錯 1、字典key-value,key是不能重復的stu_info={"name":"王誌華","age":18,"addr":"北京"}2、取值,查print(stu_info[‘name‘])print(stu
HDU 1686 Oulipo (hash演算法)
Oulipo Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2553
redis常見型別資料與操作(除String型別資料)
Hash型別 Hash是一個String型別的field和value之間的對映表,即redis的Hash資料型別的key(hash表名稱)對應的value實際的內部儲存結構為一個HashMap,因此Hash特別適合儲存物件。相對於把一個物件的每個屬性儲存為String型別,將整個物件儲存在Has