哈希對象的編碼可以是?`ziplist`?或者?`hashtable`?。
`ziplist`?編碼的哈希對象使用壓縮列表作為底層實現, 每當有新的鍵值對要加入到哈希對象時, 程序會先將保存了鍵的壓縮列表節點推入到壓縮列表表尾, 然后再將保存了值的壓縮列表節點推入到壓縮列表表尾, 因此:
* 保存了同一鍵值對的兩個節點總是緊挨在一起, 保存鍵的節點在前, 保存值的節點在后;
* 先添加到哈希對象中的鍵值對會被放在壓縮列表的表頭方向, 而后來添加到哈希對象中的鍵值對會被放在壓縮列表的表尾方向。
舉個例子, 如果我們執行以下?HSET?命令, 那么服務器將創建一個列表對象作為?`profile`?鍵的值:
~~~
redis> HSET profile name "Tom"
(integer) 1
redis> HSET profile age 25
(integer) 1
redis> HSET profile career "Programmer"
(integer) 1
~~~
如果?`profile`?鍵的值對象使用的是?`ziplist`?編碼, 那么這個值對象將會是圖 8-9 所示的樣子, 其中對象所使用的壓縮列表如圖 8-10 所示。


另一方面,?`hashtable`?編碼的哈希對象使用字典作為底層實現, 哈希對象中的每個鍵值對都使用一個字典鍵值對來保存:
* 字典的每個鍵都是一個字符串對象, 對象中保存了鍵值對的鍵;
* 字典的每個值都是一個字符串對象, 對象中保存了鍵值對的值。
舉個例子, 如果前面?`profile`?鍵創建的不是?`ziplist`?編碼的哈希對象, 而是?`hashtable`?編碼的哈希對象, 那么這個哈希對象應該會是圖 8-11 所示的樣子。

## 編碼轉換
當哈希對象可以同時滿足以下兩個條件時, 哈希對象使用?`ziplist`?編碼:
1. 哈希對象保存的所有鍵值對的鍵和值的字符串長度都小于?`64`?字節;
2. 哈希對象保存的鍵值對數量小于?`512`?個;
不能滿足這兩個條件的哈希對象需要使用?`hashtable`?編碼。
注意
這兩個條件的上限值是可以修改的, 具體請看配置文件中關于?`hash-max-ziplist-value`?選項和?`hash-max-ziplist-entries`?選項的說明。
對于使用?`ziplist`?編碼的列表對象來說, 當使用?`ziplist`?編碼所需的兩個條件的任意一個不能被滿足時, 對象的編碼轉換操作就會被執行: 原本保存在壓縮列表里的所有鍵值對都會被轉移并保存到字典里面, 對象的編碼也會從?`ziplist`?變為?`hashtable`?。
以下代碼展示了哈希對象因為鍵值對的鍵長度太大而引起編碼轉換的情況:
~~~
# 哈希對象只包含一個鍵和值都不超過 64 個字節的鍵值對
redis> HSET book name "Mastering C++ in 21 days"
(integer) 1
redis> OBJECT ENCODING book
"ziplist"
# 向哈希對象添加一個新的鍵值對,鍵的長度為 66 字節
redis> HSET book long_long_long_long_long_long_long_long_long_long_long_description "content"
(integer) 1
# 編碼已改變
redis> OBJECT ENCODING book
"hashtable"
~~~
除了鍵的長度太大會引起編碼轉換之外, 值的長度太大也會引起編碼轉換, 以下代碼展示了這種情況的一個示例:
~~~
# 哈希對象只包含一個鍵和值都不超過 64 個字節的鍵值對
redis> HSET blah greeting "hello world"
(integer) 1
redis> OBJECT ENCODING blah
"ziplist"
# 向哈希對象添加一個新的鍵值對,值的長度為 68 字節
redis> HSET blah story "many string ... many string ... many string ... many string ... many"
(integer) 1
# 編碼已改變
redis> OBJECT ENCODING blah
"hashtable"
~~~
最后, 以下代碼展示了哈希對象因為包含的鍵值對數量過多而引起編碼轉換的情況:
~~~
# 創建一個包含 512 個鍵值對的哈希對象
redis> EVAL "for i=1, 512 do redis.call('HSET', KEYS[1], i, i) end" 1 "numbers"
(nil)
redis> HLEN numbers
(integer) 512
redis> OBJECT ENCODING numbers
"ziplist"
# 再向哈希對象添加一個新的鍵值對,使得鍵值對的數量變成 513 個
redis> HMSET numbers "key" "value"
OK
redis> HLEN numbers
(integer) 513
# 編碼改變
redis> OBJECT ENCODING numbers
"hashtable"
~~~
## 哈希命令的實現
因為哈希鍵的值為哈希對象, 所以用于哈希鍵的所有命令都是針對哈希對象來構建的, 表 8-9 列出了其中一部分哈希鍵命令, 以及這些命令在不同編碼的哈希對象下的實現方法。
* * *
表 8-9 哈希命令的實現
| 命令 | `ziplist`?編碼實現方法 | `hashtable`?編碼的實現方法 |
| --- | --- | --- |
| HSET | 首先調用?`ziplistPush`?函數, 將鍵推入到壓縮列表的表尾, 然后再次調用?`ziplistPush`?函數, 將值推入到壓縮列表的表尾。 | 調用?`dictAdd`?函數, 將新節點添加到字典里面。 |
| HGET | 首先調用?`ziplistFind`?函數, 在壓縮列表中查找指定鍵所對應的節點, 然后調用?`ziplistNext`?函數, 將指針移動到鍵節點旁邊的值節點, 最后返回值節點。 | 調用?`dictFind`?函數, 在字典中查找給定鍵, 然后調用`dictGetVal`?函數, 返回該鍵所對應的值。 |
| HEXISTS | 調用?`ziplistFind`?函數, 在壓縮列表中查找指定鍵所對應的節點, 如果找到的話說明鍵值對存在, 沒找到的話就說明鍵值對不存在。 | 調用?`dictFind`?函數, 在字典中查找給定鍵, 如果找到的話說明鍵值對存在, 沒找到的話就說明鍵值對不存在。 |
| HDEL | 調用?`ziplistFind`?函數, 在壓縮列表中查找指定鍵所對應的節點, 然后將相應的鍵節點、 以及鍵節點旁邊的值節點都刪除掉。 | 調用?`dictDelete`?函數, 將指定鍵所對應的鍵值對從字典中刪除掉。 |
| HLEN | 調用?`ziplistLen`?函數, 取得壓縮列表包含節點的總數量, 將這個數量除以?`2`?, 得出的結果就是壓縮列表保存的鍵值對的數量。 | 調用?`dictSize`?函數, 返回字典包含的鍵值對數量, 這個數量就是哈希對象包含的鍵值對數量。 |
| HGETALL | 遍歷整個壓縮列表, 用?`ziplistGet`?函數返回所有鍵和值(都是節點)。 | 遍歷整個字典, 用?`dictGetKey`?函數返回字典的鍵, 用`dictGetVal`?函數返回字典的值。 |
- 介紹
- 前言
- 致謝
- 簡介
- 第一部分:數據結構與對象
- 簡單動態字符串
- SDS 的定義
- SDS 與 C 字符串的區別
- SDS API
- 重點回顧
- 參考資料
- 鏈表
- 鏈表和鏈表節點的實現
- 鏈表和鏈表節點的 API
- 重點回顧
- 字典
- 字典的實現
- 哈希算法
- 解決鍵沖突
- rehash
- 漸進式 rehash
- 字典 API
- 重點回顧
- 跳躍表
- 跳躍表的實現
- 跳躍表 API
- 重點回顧
- 整數集合
- 整數集合的實現
- 升級
- 升級的好處
- 降級
- 整數集合 API
- 重點回顧
- 壓縮列表
- 壓縮列表的構成
- 壓縮列表節點的構成
- 連鎖更新
- 壓縮列表 API
- 重點回顧
- 對象
- 對象的類型與編碼
- 字符串對象
- 列表對象
- 哈希對象
- 集合對象
- 有序集合對象
- 類型檢查與命令多態
- 內存回收
- 對象共享
- 對象的空轉時長
- 重點回顧
- 第二部分:單機數據庫的實現
- 數據庫
- 數據庫鍵空間
- 重點回顧
- RDB 持久化
- RDB 文件結構
- 重點回顧
- AOF 持久化
- AOF 持久化的實現
- 重點回顧
- 事件
- 文件事件
- 重點回顧
- 參考資料
- 客戶端
- 客戶端屬性
- 重點回顧
- 服務器
- 命令請求的執行過程
- 重點回顧
- 第三部分:多機數據庫的實現
- 復制
- 舊版復制功能的實現
- 重點回顧
- Sentinel
- 啟動并初始化 Sentinel
- 重點回顧
- 參考資料
- 集群
- 節點
- 重點回顧
- 第四部分:獨立功能的實現
- 發布與訂閱
- 頻道的訂閱與退訂
- 重點回顧
- 參考資料
- 事務
- 事務的實現
- 重點回顧
- Lua 腳本
- 創建并修改 Lua 環境
- 重點回顧
- 排序
- SORT <key> 命令的實現
- 重點回顧
- 二進制位數組
- GETBIT 命令的實現
- 重點回顧
- 慢查詢日志
- 慢查詢記錄的保存
- 慢查詢日志的閱覽和刪除
- 添加新日志
- 重點回顧
- 監視器
- 成為監視器
- 向監視器發送命令信息
- 重點回顧
- 源碼、相關資源和勘誤