Redis數據類型之Hash(二)

来源:http://www.cnblogs.com/programlearning/archive/2017/06/05/6944572.html
-Advertisement-
Play Games

前言: Redis hash是一個String類型的field和value的映射表。添加、刪除操作複雜度平均為O(1),為什麼是平均呢?因為Hash的內部結構包含zipmap和hash兩種。hash特別適合用於存儲對象。相對於將對象序列化存儲為String類型,將一個對象存儲在hash類型中會占用更 ...


前言:

     Redis hash是一個String類型的field和value的映射表。添加、刪除操作複雜度平均為O(1),為什麼是平均呢?因為Hash的內部結構包含zipmap和hash兩種。hash特別適合用於存儲對象。相對於將對象序列化存儲為String類型,將一個對象存儲在hash類型中會占用更少的記憶體,並且可以方便的操作對象。為什麼省記憶體,因為對象剛開始使用zipmap存儲的。

    1. zipmap

        zipmap其實並不是hashtable,zip可以節省hash本身需要的一些元數據開銷。zipmap的添加、刪除、查找複雜度為O(n),但是filed數量都不多,所以可以說平均是O(1)。

        預設配置:
            hash-max-ziplist-entries 512  //filed最多512個
            hash-max-ziplist-value 64     //value最大64位元組

        記憶體分配如下:

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

          (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的結束,0xFF

        zipmap創建:

    2.hash

       在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"

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Fiddler抓包神器使用教程 掃盲篇 轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/72823370 本文出自 "【趙彥軍的博客】" 1、什麼是抓包? 不同主機之間的數據通信都是通過網路來進行傳輸,對那些在網路上傳輸的數據(發送、 ...
  • 使用的插件參照地址:https://github.com/xu-li/cordova-plugin-wechat;(這裡包含微信登錄,微信分享和微信支付) 插件安裝 cordova plugin add cordova-plugin-wechat --variable wechatappid=YOU ...
  • 緩存組件應該說是每個客戶端程式必備的核心組件,試想對於每個界面的訪問都必須重新請求勢必降低用戶體驗。但是如何處理客戶端緩存貌似並沒有統一的解決方案,多數開發者選擇自行創建資料庫直接將伺服器端請求的JSON(或Model)緩存起來,下次請求則查詢資料庫檢查緩存是否存在;另外還有些開發者會選擇以歸檔文件... ...
  • 檢查 minSdkVersion什麼的是不是和你依賴的包一樣,它上面也有個小提示,顯示本地的11,依賴的為15,那就改成15好了,重新build好了 ClassNotFoundException異常 看提示信息貌似是配置文件的路徑或者什麼錯了,其實就是你的v7包之類的版本不對,要跟sdk版本一致或者 ...
  • 目錄 一、MySQL概述 二、下載安裝 三、資料庫操作 四、數據表操作 五、表內容操作 一、MySQL概述 MySQL是一個關係型資料庫管理系統,由瑞典MySQL AB 公司開發,目前屬於 Oracle 旗下產品。MySQL 是最流行的關係型資料庫管理系統之一,在 WEB 應用方面,MySQL是最好 ...
  • 『實踐』VirtualBox 5.1.18+Centos 6.8+hadoop 2.7.3搭建hadoop完全分散式集群 1.基本設定和軟體版本 主機名 ip 對應角色 master 192.168.56.4 NameNode slave1 192.168.56.3 DataNode1 slave2 ...
  • 很多時候,我們經常使用sp_spaceused來查看表的空間使用情況,上個月群里有個網友說他使用DELETE刪除了數據後,使用sp_spaceused查看,發現該表的分配的空間總量(reserved)與數據使用的空間總量(data)沒有變化,當時和他討論了並分析了一下原因,隨手記錄了一下這個案例,這... ...
  • 2017年6月5日,天氣——雨。 前兩天整理之前的學習筆記時,發現對事務併發產生的問題——臟讀、幻讀、不可重覆讀和丟失更新這些概念有點模糊,於是又重新溫習了一遍,現在把自己的一些理解歸納整理如下,方便大家學習。 鎖就是防止其他事務訪問指定資源的手段。鎖是實現併發控制的主要方法,是多個用戶能夠同時操縱 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...