Redis 學習筆記(篇二):字典

来源:https://www.cnblogs.com/wind-snow/archive/2019/06/19/11054547.html
-Advertisement-
Play Games

字典 字典又稱為符號表、關聯數組或映射(map),是一種用於保存鍵值對(key value)的數據結構。 那麼 C 語言中有沒有這樣 key value 型的內置數據結構呢? 答案:沒有。 說起鍵值對,是不是想到了 Java 中的 Map?Java中的 Map 實現有兩個:HashMap 和 Tre ...


字典

字典又稱為符號表、關聯數組或映射(map),是一種用於保存鍵值對(key-value)的數據結構。
那麼 C 語言中有沒有這樣 key-value 型的內置數據結構呢? 答案:沒有。

說起鍵值對,是不是想到了 Java 中的 Map?Java中的 Map 實現有兩個:HashMap 和 TreeMap。
HashMap的底層是 hash 表,TreeMap 的底層是二叉搜索樹,而 Redis 必須要求的一點就是效率,所以 Redis 中的字典使用的是 hash 表。

那麼下麵我們就拿 Redis 中的字典與 Java 中的 HashMap 對比一下

  • Redis 定義的字典結構是什麼?Java 的呢?
  • Java 和 Redis 的字元串結構有什麼區別?

Redis 中字典的數據結構是什麼?

Redis 中字典的底層使用的 hash 表,它的具體結構如下:

    /*
    * 字典
    *
    * 每個字典使用兩個哈希表,用於實現漸進式 rehash
    */
    typedef struct dict {

        // 特定於類型的處理函數
        dictType *type;

        // 類型處理函數的私有數據
        void *privdata;

        // 哈希表(2 個)
        dictht ht[2];

        // 記錄 rehash 進度的標誌,值為 -1 表示 rehash 未進行,否則表示 rehash 進行到了 ht[0] 具體索引上
        int rehashidx;

    } dict;
    /*
    * 哈希表
    */
    typedef struct dictht {

        // 哈希表節點指針數組(俗稱桶,bucket)
        dictEntry **table;

        // 指針數組的大小
        unsigned long size;

        // 指針數組的長度掩碼,用於計算索引值
        // 總是等於 size - 1
        unsigned long sizemask;

        // 哈希表現有的節點數量
        unsigned long used;

    } dictht;
    /*
    * 哈希表節點
    */
    typedef struct dictEntry {

        // 鍵
        void *key;

        // 值
        union {
            void *val;
            uint64_t u64;
            int64_t s64;
        } v;

        // 鏈往後繼節點
        struct dictEntry *next;

    } dictEntry;

哈希表的基本結構這裡就不再解釋了,不知道的可以百度一下。這裡解釋一下 dict 中的內容,如下(出自《Redis設計與實現第二版》第四章:字典):

《Redis設計與實現第二版》26頁

Java 中 HashMap 的具體實現就不多說了,結構和上面不一樣。

Java 中 HashMap 和 redis 中字典的比較

Hash 演算法的比較

當要將一個新的鍵值對添加到字典裡面時,需要先根據鍵值對的鍵計算出哈希值和索引值,然後再根據索引值,將包含新鍵值對的哈希表節點放到哈希表數組的指定索引下麵。
Redis 是通過 MurmurHash2 演算法求出 key 的 hash 值;Java 是通過引入 hashCode 的概念計算 hash 值。

解決 hash 衝突的方法

當有兩個或以上的鍵值對被分配到 hash 表數組的同一個索引上面時,即發生了 hash 衝突。那麼 Redis 如何解決的 hash 衝突呢?
這就用到 dictEntry 結構的 next 節點了,通過 next 將 hash 表節點串起來。例:k1-v1 和 k2-v2 分配到了同一個索引下,示意圖如下(出自《Redis設計與實現第二版》第四章:字典):

《Redis設計與實現第二版》29頁

所以,理論上來說在特殊情況下 hash 會退化成鏈表。而 Java 為瞭解決 hash 表退化的問題,在 JDK1.8 的 HashMap 中引入了紅黑樹。

擴容與收縮(rehash)

隨著操作的不斷進行,哈希表保存的鍵值對會逐漸的增多或減少,如果hash表保存的鍵值對過多,會影響查詢的效率;反之,如果過小,又會浪費空間。所以程式就需要對哈希表的大小進行相應的擴容或者收縮。

無論擴容還是收縮,都涉及到三個問題:
(1)觸發條件是什麼?是手動觸發還是自動觸發。
(2)擴容或者收縮的規則是什麼?具體過程是怎樣的?
(3)當哈希表在擴容時,是否允許別的線程來操作這個哈希表?

** Redis 的實現**

Redis 中的擴展和收縮是通過 rehash 操作完成的,擴容的觸發條件是:
1) 當伺服器沒有執行 BGSAVE 命令或者 BGREWRITEAOF 命令時,負載因數大於等於 1 時觸發。
2) 當伺服器正在執行 BGSAVE 命令或者 BGREWRITEAOF 命令時,負載因數大於等於 5 時觸發。

其中:哈希表的負載因數 = 哈希表已保存節點數量 / 哈希表總長度。
收縮的條件是:哈希表的負載因數 < 0.1。

擴容的規則是:擴容至第一個大於等於 ht[0].used(當前包含鍵值對數量)* 2 的 2^n;
收縮的規則是:收縮至第一個大於等於 ht[0].used(當前包含鍵值對數量)* 2 的 2^n;
擴容和收縮的過程一樣,都是:
1) 為字典的 ht[1] 哈希表分配空間。
2) 將保存在 ht[0] 中的所有鍵值對 rehash 到 ht[1] 上。
3) 釋放 ht[0] ,ht[1] = ht[0],再為 ht[1] 新創建一個空的哈希表。

當 Redis 中的哈希表在擴容時,必須得能允許別的請求來操作哈希表。否則一旦要rehash一個很大的哈希表時,就得拒絕所有對該哈希表的請求,這是不被允許的。所以 Redis 在上面的第二步(rehash)是漸進式 rehash 的。rehash的詳細步驟如下(出自《Redis設計與實現第二版》第四章:字典):
《Redis設計與實現第二版》33頁

因為在進行漸進式 rehash 的過程中,字典會同時使用 ht[0] 和 ht[l] 兩個哈希表,所以在漸進式 rehash 進行期間,字典的刪除(delete)、査找(find)、更新(update)等操作,會在兩個哈希表上進行。例如,要在字典裡面査找一個鍵的話,程式會先在ht[0]裡面進行査找,如果沒找到的話,就會繼續到ht[l]裡面進行査找,諸如此類。
另外,在漸進式 rehash 執行期間,新添加到字典的鍵值對一律會被保存到 ht[l] 裡面, 而 ht[0] 則不再進行任何添加操作,這一措施保證了 ht[0] 包含的鍵值對數量會只減不 增,並隨著 rehash 操作的執行而最終變成空表。

漸進式 rehash 的好處在於既將哈希表的擴容操作變成了線程安全的,又把計算量分攤到了對字典的每個增刪改查操作上面。

上期思考問題

上一篇遺留的問題:
Redis 中的 SDS(Simple Dynamic String)、Java中的 (StringBuilder、StringBuffer、ArrayList ),他們的擴容規則分別是什麼呢?

Redis 中的 SDS 的擴容規則是:

  • 如果對 SDS 進行修改之後, SDS 的長度(也即是 len 屬性的值)將小於1M,那麼程式分配和 len 屬性同樣大小的未使用空間,這時 SDS len 屬性的值將和 free 屬性的值相同。舉個例子,如果進行修改之後, SDS 的 len 將變成 13 位元組,那麼程式也會分配 13 位元組的未使用空間, SDS 的 buf 數組的實際長度將變成 13 + 13 + 1 = 27 位元組(額外的一位元組用於保存空字元)。
  • 如果對 SDS 進行修改之後, SDS 的長度將大於等於 1MB ,那麼程式會分配 1M 的未使用空間。舉個例子,如果進行修改之後,SDS 的 len 將變成 30MB,那麼程式會分配 1M 的未使用空間,SDS 的 buf 數組的實際長度將為 30MB + 1MB + 1byte。

Java中:

  • StringBuilder 和 StringBuffer 都繼承了 AbstractStringBuilder,擴容規則都是:擴容至 newCapacity 和 oldCapacity * 2 + 2 的最大值。部分代碼如下:

      private int newCapacity(int minCapacity) {
          // overflow-conscious code
          int newCapacity = (value.length << 1) + 2;
          if (newCapacity - minCapacity < 0) {
              newCapacity = minCapacity;
          }
          return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
              ? hugeCapacity(minCapacity)
              : newCapacity;
      }
  • ArrayList:如果容量允許的情況下擴容至 newCapacity 和 oldCapacity + oldCapacity / 2 的最大值。 部分代碼如下:

      private void grow(int minCapacity) {
          // overflow-conscious code
          int oldCapacity = elementData.length;
          int newCapacity = oldCapacity + (oldCapacity >> 1);
          if (newCapacity - minCapacity < 0)
              newCapacity = minCapacity;
          if (newCapacity - MAX_ARRAY_SIZE > 0)
              newCapacity = hugeCapacity(minCapacity);
          // minCapacity is usually close to size, so this is a win:
          elementData = Arrays.copyOf(elementData, newCapacity);
      }

本期思考問題:

本篇介紹了 Redis 中有關擴容和收縮的3個問題,那麼對應的 Java 中 HashMap 關於擴容和收縮的問題又是怎麼處理的呢?


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

-Advertisement-
Play Games
更多相關文章
  • 今天在創建資料庫的時候,遇到了沒有創建資料庫許可權的問題,後來百度了一下解決了該問題。 1.先用windows身份驗證登錄,在安全性下麵的找到自己創建的登錄名,雙擊,在彈出的對話框中為它賦予許可權。 2.設置完後退出,然後登錄,這樣就可以創建資料庫了 ...
  • 大數據如此火熱的現在,想必許多小伙伴都想要加入這個行業。也是我們今天就要拿出收藏已久的大數據學習計劃。幫助你不走彎路,邁向大數據 1 大數據應用離不開基礎軟體的支撐,且大部分大數據組件部署在 Linux 操作系統上的用戶空間,也有很多組件也借鑒了Linux 操作系統的一些設計精髓,所以 Linux ...
  • MongoDB 是一款開源、跨平臺、分散式,具有大數據處理能力的文檔存儲資料庫。在 2007 年由 MongoDB 軟體公司開發完成,並實現全部代碼源發展。目 前,該文檔資料庫被國內外眾多知名網因所採納,用於提高數據訪問的處理速度 和大數據存儲問題。 基本操作命令 : show dbs : 顯示所有 ...
  • 找出employee表的所有外鍵約束 Result: 此時如果想刪除和posId相關的外鍵,只需要 找出以employee為REFERENCED_TABLE的所有約束 Result: ...
  • 開發同事反饋一個SQL Server存儲過程執行的時候,報“鏈接伺服器"(null)"的 OLE DB 訪問介面 "SQLNCLI10" 返回了消息 "Cannot start more transactions on this session."。這個存儲過程,個人做了一個精簡和脫敏處理後如下: ... ...
  • 大數據時代,給想從事IT的人帶來了新的發展機會,也提供了新的職業發展通道。在面對眾多的大數據就業崗位,我們應該選擇什麼樣的職業發展方向,並去學習相應技能達到企業要求呢? ...
  • Oracle 伺服器主要又實例、資料庫、程式全局區和前臺進程組成。 實例可以進一步劃分為系統全局區(SGA)和後臺進程(PMON、SMON等)兩部分,其中,SGA 使用操作系統的記憶體資源,而後臺進程需要使用 CPU 與記憶體資源。資料庫(Database)中包含數據文件(Data files)、控制文 ...
  • 題目 編寫一個 SQL 查詢,獲取 表中第二高的薪水(Salary) 。 例如上述 表,SQL查詢應該返回 作為第二高的薪水。如果不存在第二高的薪水,那麼查詢應返回 。 解決方案 方法一 :使用 和 子句 將不同的薪資按降序排序,然後使用 子句獲得第二高的薪資。 然而,如果沒有這樣的第二最高工資,這 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...