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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...