Redis數據類型之Hash(二)

前言:redis

     Redis hash是一個String類型的field和value的映射表。添加、刪除操做複雜度平均爲O(1),爲何是平均呢?由於Hash的內部結構包含zipmap和hash兩種。hash特別適合用於存儲對象。相對於將對象序列化存儲爲String類型,將一個對象存儲在hash類型中會佔用更少的內存,而且能夠方便的操做對象。爲何省內存,由於對象剛開始使用zipmap存儲的。數據結構

    1. zipmapui

        zipmap其實並非hashtable,zip能夠節省hash自己須要的一些元數據開銷。zipmap的添加、刪除、查找複雜度爲O(n),可是filed數量都很少,因此能夠說平均是O(1)。this

        默認配置:
            hash-max-ziplist-entries 512  //filed最多512個
            hash-max-ziplist-value 64     //value最大64字節spa

        內存分配以下:3d

            例:"foo" => "bar", "hello" => "world":<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"code

          (1)zmlen:記錄當前zipmap的key-value對的數量。一個字節,所以規定其表示的數量只能爲0~254,當zmlen>254時,就須要遍歷整個zipmap來獲得key-value對的個數
          (2)len:記錄key或value的長度,有兩種狀況,當len的第一個字節爲0~254(註釋是253,咱們以代碼爲準)時,那麼len就只佔用這一個字節。若len的第一個字節爲254時,那麼len將用後面的4個字節來表示。所以len要麼佔用1字節,要麼佔用5字節。
          (3)free:記錄value後面的空閒字節數,將」foo」 => 「world」變爲」foo」 => 「me」 ,那麼會致使3個字節的空閒空間。當free的字節數過大用1個字節不足以表示時,zipmap就會從新分配內存,保證字符串儘可能緊湊。
          (4)end: 記錄zipmap的結束,0xFFserver

        zipmap建立:對象

    2.hashblog

       在Redis中,hash表被稱爲字典(dictionary),採用了典型的鏈式解決衝突方法,即:當有多個key/value的key的映射值(每對key/value保存以前,會先經過相似HASH(key) MOD N的方法計算一個值,
  以便肯定其對應的hash table的位置)相同時,會將這些value以單鏈表的形式保存;同時爲了控制哈希表所佔內存大小,redis採用了雙哈希表(ht[2])結構,並逐步擴大哈希表容量(桶的大小)的策略,
  即:剛開始,哈希表ht[0]的桶大小爲4,哈希表ht[1]的桶大小爲0,待衝突嚴重(redis有必定的判斷條件)後,ht[1]中桶的大小增爲ht[0]的兩倍,並逐步(注意這個詞:」逐步」)將哈希表ht[0]中元素遷移(稱爲「再次Hash」)到ht[1],
  待ht[0]中全部元素所有遷移到ht[1]後,再將ht[1]交給ht[0](這裏僅僅是C語言地址交換),以後重複上面的過程。

     

  Redis哈希表的實現位於文件dict.h和dict.c中,主要數據結構以下:

#define DICT_NOTUSED(V) ((void) V)

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 table. */
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;

  基本操做:
        Redis中hash table主要有如下幾個對外提供的接口:dictCreate、dictAdd、dictReplace、dictDelete、dictFind、dictEmpty等,而這些接口調用了一些基礎操做,包括:_dictRehashStep,_dictKeyIndex等

        Hash Table在必定狀況下會觸發rehash操做,即:將第一個hash table中的數據逐步轉移到第二個hash table中。
      (1)觸發條件 當第一個表的元素數目大於桶數目且元素數目與桶數目比值大於5時,hash 表就會擴張,擴大後新表的大小爲舊錶的2倍。

/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
    /* Incremental rehashing already in progress. Return. */
    if (dictIsRehashing(d)) return DICT_OK;

    /* If the hash table is empty expand it to the initial size. */
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    /* If we reached the 1:1 ratio, and we are allowed to resize the hash
     * table (global setting) or we should avoid it but the ratio between
     * elements/buckets is over the "safe" threshold, we resize doubling
     * the number of buckets. */
    if (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
    {
        return dictExpand(d, d->ht[0].used*2);
    }
    return DICT_OK;
}

      (2)轉移策略 爲了不一次性轉移帶來的開銷,Redis採用了平攤開銷的策略,即:將轉移代價平攤到每一個基本操做中,如:dictAdd、dictReplace、dictFind中,每執行一次這些基本操做會觸發一個桶中元素的遷移操做。在此,有讀者可能會問,若是這樣的話,若是舊hash table很是大,何時才能遷移完。爲了提升前移速度,Redis有一個週期性任務serverCron,每隔一段時間會遷移100個桶。

    相關操做:

         1.hset,hmset,hsetnx
            hset命令用來將某個hash指定鍵的值,若是鍵不存在,則建立並設置對應的值,返回一個整數1,若是鍵已經存在,則對應的值將被覆蓋並返回整數0.
            hset hash_name field value 

127.0.0.1:6379> hset userid:1000 age 100
(integer) 1
127.0.0.1:6379> hset userid:1000 age 10
(integer) 0

            hmset命令和hset命令的做用類似,能夠用來設置hash的鍵和值。不一樣的是hmset能夠同時設置多個鍵值對。操做成功後hmset命令返回一個簡單的字符串「OK」。
            hset hash_name field value

127.0.0.1:6379> hmset userid:1000 name zhangsan age 10
OK

            hsetnx命令也用來在指定鍵不存在的狀況下設置鍵值信息。若是鍵不存在,則Redis會先建立鍵,而後設置對應的值,操做成功後返回整數1。若是該鍵已經存在,則該命令不進行任何操做,返回值爲0
            hsetnx hash_name field value

127.0.0.1:6379> HSETNX userid:1000 age 10
(integer) 0
127.0.0.1:6379> HSETNX userid:1000 weight 100
(integer) 1

         2.hget,hmget,hgetall
            hget命令用來獲取某個hash指定key的值。若是該鍵存在,直接返回對應的值,不然返回nil。
            hget hash_name field

127.0.0.1:6379> hget user:1000 name
(nil)
127.0.0.1:6379> hget userid:1000 name
"zhangsan"

          hmget命令和hget命令相似,用來返回某個hash多個鍵的值的列表,對於不存在的鍵,返回nil值。
          hmget hash_name field1 field2...

127.0.0.1:6379> hmget userid:1000 name age
1) "zhangsan"
2) "10"

          hgetall命令返回一個列表,該列表包含了某個hash的全部鍵和值。在返回值中,先是鍵,接下來的一個元素是對應的值,因此hgetall命令返回的列表長度是hash大小的兩倍。
          hgetall hash_name

127.0.0.1:6379> HGETALL userid:1000
1) "age"
2) "10"
3) "name"
4) "zhangsan"
5) "weight"
6) "100"

        3.hexists
           hexists命令用來判斷某個hash指定鍵是否存在,若存在返回整數1,不然返回0。
           hexists hash_name field

127.0.0.1:6379> HEXISTS userid:1000 name
 integer) 1
127.0.0.1:6379> HEXISTS userid:1000 sex
(integer) 0

        4.hlen
           hlen命令用來返回某個hash中全部鍵的數量。
           hlen hash_name

127.0.0.1:6379> hlen userid:1000
(integer) 3

         5.hdel
            hdel命令用來刪除某個hash指定的鍵。若是該鍵不存在,則不進行任何操做。hdel命令的返回值是成功刪除的鍵的數量(不包括不存在的鍵)。
            hdel hash_name field      

127.0.0.1:6379> hlen userid:1000
(integer) 3
127.0.0.1:6379> hdel userid:1000 age
(integer) 1
127.0.0.1:6379> hlen userid:1000
(integer) 2

         6.Hkeys,hvals
            hkeys命令返回某個hash的全部鍵,若是該hash不存在任何鍵則返回一個空列表。
            hkeys hash_name
            hvals命令返回某個hash的全部值的列表。
            hvals hash_name

127.0.0.1:6379> hkeys userid:1000
1) "name"
2) "weight"
127.0.0.1:6379> hvals userid:1000
1) "zhangsan"
2) "100"

         7.hincrby,hincrbyfloat
            這兩個命令都用來對指定鍵進行增量操做,不一樣的是hincrby命令每次加上一個整數值,而hincrbyfloat命令每次加上一個浮點值。操做成功後返回增量操做後的最終值
            hincrby hash_name field i
            hincrbyfloat hash_name field f

127.0.0.1:6379> HINCRBY userid:1000 weight 10
(integer) 110
127.0.0.1:6379> HINCRBYFLOAT userid:1000 weight 10.0
"120"
相關文章
相關標籤/搜索