15天玩轉redis —— 第四篇 雜湊物件型別

一線碼農發表於2015-11-18

  redis中的hash也是我們使用中的高頻資料結構,它的構造基本上和程式語言中的HashTable,Dictionary大同小異,如果大家往後有什麼邏輯需要用

Dictionary存放的話,可以根據場景優先考慮下redis哦,起碼可以裝裝逼嘛,現在我預設你已經有裝逼的衝動了,開啟redis手冊,看看有哪些我們用得到

的裝逼方法。

 

一:常用方法

  只要是一個資料結構,最基礎的永遠是CURD,redis中的insert和update,永遠只需要set來替代,比如下面的Hset,如下圖:

前面幾篇文章我都沒有挑選一個方法仔細講解,其實也沒什麼好講解的,就好似C#中的一個類的一個方法而已,知道傳遞一些啥引數就OK了,就比如要

說的HSet,它的格式如下:

接下來我在CentOS裡面操作一下,

[administrator@localhost redis-3.0.5]$ src/redis-cli
127.0.0.1:6379> clear

127.0.0.1:6379> hset person name jack
(integer) 1
127.0.0.1:6379> hset person age 20
(integer) 1
127.0.0.1:6379> hset person sex famale
(integer) 1
127.0.0.1:6379> hgetall person
1) "name"
2) "jack"
3) "age"
4) "20"
5) "sex"
6) "famale"
127.0.0.1:6379> hkeys person
1) "name"
2) "age"
3) "sex"
127.0.0.1:6379> hvals person
1) "jack"
2) "20"
3) "famale"
127.0.0.1:6379> 

或許有人看了上面的console有一點疑惑,那就是前面有幾個引數,比如person,name啦,然後才是value,如果你看了第一篇的話,你大概就明白了,

其實在redis的這個層面,它永遠只有一個鍵,一個值,這個鍵永遠都是字串物件,也就是SDS物件,而值的種類就多了,有字串物件,有佇列物件,

還有這篇的hash物件,往後的有序集合物件等等,如果你還不明白的話,轉化為C#語言就是。

1 var person=new Dictionary<string,string>();
2 person.Add("name","jack");
3 ....

呼叫方法就是這麼的簡單,關鍵在於時不時的需要你看一看手冊,其實最重要的是瞭解下它在redis原始碼中的原理就好了。

 

二:探索原理

  hash的原始碼是在dict.h原始碼裡面,列舉如下:

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

typedef struct dictType {
    unsigned int (*hashFunction)(const void *key);
    void *(*keyDup)(void *privdata, const void *key);
    void *(*valDup)(void *privdata, const void *obj);
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    void (*keyDestructor)(void *privdata, void *key);
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new 0. */
typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int iterators; /* number of iterators currently running */
} dict;

/* If safe is set to 1 this is a safe iterator, that means, you can call
 * dictAdd, dictFind, and other functions against the dictionary even while
 * iterating. Otherwise it is a non safe iterator, and only dictNext()
 * should be called while iterating. */
typedef struct dictIterator {
    dict *d;
    long index;
    int table, safe;
    dictEntry *entry, *nextEntry;
    /* unsafe iterator fingerprint for misuse detection. */
    long long fingerprint;
} dictIterator;

上面就是我們使用hash的原始碼資料結構,接下來我來擼一擼其中的邏輯關係。

 

1. dict結構

1 typedef struct dict {
2     dictType *type;
3     void *privdata;
4     dictht ht[2];
5     long rehashidx; /* rehashing not in progress if rehashidx == -1 */
6     int iterators; /* number of iterators currently running */
7 } dict;

這個結構是hash的真正的底層資料結構,可以看到其中有5個屬性。

 

<1> dictType *type

   可以看到它的型別是dictType,從上面你也可以看到,它是有列舉結構定義的,如下:

1 typedef struct dictType {
2     unsigned int (*hashFunction)(const void *key);
3     void *(*keyDup)(void *privdata, const void *key);
4     void *(*valDup)(void *privdata, const void *obj);
5     int (*keyCompare)(void *privdata, const void *key1, const void *key2);
6     void (*keyDestructor)(void *privdata, void *key);
7     void (*valDestructor)(void *privdata, void *obj);
8 } dictType;

 

從上面這個資料結構中你可以看到裡面都是一些方法,但是有一個非常重要的方法,那就是第一個hashFunction,可以看到它就是計算hash值的,

跟C#中的dictionary中求hash值一樣一樣的。

 

<2> dictht ht[2]

       你可能會疑問,為什麼這個屬性是2個大小的陣列呢,其實正真使用的是ht[0],而ht[1]是用於擴容hash表時的暫存陣列,這一點也很奇葩,

同時也很精妙,redis為什麼會這麼做呢???仔細想想你可能會明白,擴容有兩種方法,要麼一次性擴容,要麼漸進性擴容,後面這種擴容是什

麼意思呢?就是我在擴容的同時不影響前端的CURD,我慢慢的把資料從ht[0]轉移到ht[1]中,同時rehashindex來記錄轉移的情況,當全部轉移

完成之後,將ht[1]改成ht[0]使用,就這麼簡單。

 

2. dicth結構

1 typedef struct dictht {
2     dictEntry **table;
3     unsigned long size;
4     unsigned long sizemask;
5     unsigned long used;
6 } dictht;

<1> dictEntry **table;

       從上面這個結構體中,你可以看到一個非常重要的屬性: dictEntry **table, 其中table是一個陣列,陣列型別是dictEntry,既然是一個陣列,

那後面的三個屬性就好理解了,size是陣列的大小,sizemask和陣列求模有關,used記錄陣列中已使用的大小,現在我們把注意力放在dictEntry這

個陣列實體型別上面。

 

3. dictEntry結構

 1 typedef struct dictEntry {
 2     void *key;
 3     union {
 4         void *val;
 5         uint64_t u64;
 6         int64_t s64;
 7         double d;
 8     } v;
 9     struct dictEntry *next;
10 } dictEntry;

從這個資料結構上面你可以看到有三個大屬性。

第一個就是:   *key:它就是hash表中的key。

第二個就是:    union的*val 就是hash的value。

第三個就是:    *next就是為了防止hash衝突採用的掛鏈手段。

這個原理和C#中的Dictionary還是一樣一樣的。

 

不知道你看懂了沒有,如果總結上面描述的話,我可以畫出如下的hash結構圖。

好了,就此打住,去公司了。

 

相關文章