1. 程式人生 > >redis中字典的add操作(hash演算法、rehash)

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開發:如何在選單呼叫控制元件如ButtonTextView……

當我們在類內定義控制元件的全域性變數時,如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

pythonlist常用操作不包括切片

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