Redis Hash 是存储对象数据的理想选择,其底层会根据数据量自动切换 ziplist 或 dict 结构以平衡内存与性能。本文将通过图解与命令演示,解析 Hash 的存储原理、冲突处理机制及核心操作,助你高效管理键值映射数据。
Hash 存储结构与对象映射
Redis Hash(哈希散列)是由字符类型的 field(字段)和 value 组成的映射表结构,类似于数据库中的表格。在 Hash 类型中,field 与 value 一一对应且不允许重复,特别适合用于存储对象数据。一个 field/value 可视为表格中的一条记录,而一个 key 则对应多条数据。
以用户数据为例,使用 user 作为 key,id:1 为字段,name:Cao 为 value,可将表格数据直接映射到内存中:
| id | name |
|---|---|
| 1 | Cao |
| 2 | Zhao |
对应的 Redis 命令操作如下:
# 以 user 为 key,设置 id+序号为字段,name+名字为值 127.0.0.1:6379> HMSET user id:1 name:Cao id:2 name:Zhao OK # 查询 user 这个 key 下所有的数据,并以字符串形式返回值 127.0.0.1:6379> HGETALL user 1) "id:1" 2) "name:Cao" 3) "id:2" 4) "name:Zhao"
注意:查询返回的 value 始终以字符串形式呈现。一个 Hash 类型的 key 最多可存储 2^32-1(约 40 亿)个字段/值对。Redis 会为每个 key 额外存储类型、最后访问时间等管理信息,因此字段越多,管理开销越大。当 Hash 中最后一个元素被移除时,该结构会自动删除并释放内存。

底层存储原理与哈希冲突解决
Hash 类型的底层存储有两种实现方式,Redis 会根据数据规模自动切换:
- ziplist(压缩列表):当数据量较小时使用。需同时满足两个条件:所有键值对的字符串长度总和小于 64 字节,且键值对数量小于 512 个。该结构内存紧凑,适合小对象。
- dict(字典结构):当不满足上述条件时切换。dict 类似于 Java 的 HashMap,采用数组与链表结合的方式存储,基于哈希表算法实现,查找时间复杂度为 O(1)。
哈希映射与冲突处理
哈希表的核心思想是将数据映射到数组的特定位置,通过下标直接访问以提升查找效率。例如,将数字 1、5、8 映射到长度为 3 的数组中,可通过取模运算(数字 % 3)确定槽位:

此时 5 和 8 均映射到同一槽位,产生“哈希冲突”。Redis 采用链表地址法解决冲突:将冲突的数据通过链表串联,查找时遍历链表即可定位目标值。

若 field 或 value 为字符串,Redis 会先通过哈希函数将其转换为数值再进行映射。这种设计在空间利用率与查找效率之间取得了良好平衡。
核心命令与实战操作
以下以微博好友关注时间场景为例,演示 Hash 的常用操作。以用户 ID user:10 为 key,好友 ID 为 field,关注时间为 value:
# 设置单个字段 127.0.0.1:6379> HSET user:10 user:1 20201001 (integer) 1 # 同时设置多个字段 127.0.0.1:6379> HMSET user:10 user:2 20201002 user:3 20201004 user:4 20201018 OK # 查询单个字段 127.0.0.1:6379> HGET user:10 user:2 "20201002" # 查询所有字段与值 127.0.0.1:6379> HGETALL user:10 1) "user:1" 2) "20201001" 3) "user:2" 4) "20201002" 5) "user:3" 6) "20201004" 7) "user:4" 8) "20201018" # 仅返回所有字段名 127.0.0.1:6379> HKEYS user:10 1) "user:1" 2) "user:2" 3) "user:3" 4) "user:4" # 返回字段个数 127.0.0.1:6379> HLEN user:10 (integer) 4 # 返回所有字段值 127.0.0.1:6379> HVALS user:10 1) "20201001" 2) "20201002" 3) "20201004" 4) "20201018" # 迭代 Hash 键值对 127.0.0.1:6379> HSCAN user:10 0 1) "0" 2) 1) "user:1" 2) "20201001" 3) "user:2" 4) "20201002" 5) "user:3" 6) "20201004" 7) "user:4" 8) "20201018" # 判断字段是否存在 127.0.0.1:6379> HEXISTS user:10 user:4 (integer) 1 127.0.0.1:6379> HEXISTS user:10 user:5 (integer) 0
常用命令速查表:
| 命令 | 说明 |
|---|---|
| HDEL key field [field...] | 删除一个或多个哈希表字段 |
| HEXISTS key field | 判断字段是否存在 |
| HGET key field | 获取指定字段的值 |
| HGETALL key | 获取所有字段与值 |
| HINCRBY key field increment | 字段值增加整数 |
| HINCRBYFLOAT key field increment | 字段值增加浮点数 |
| HKEYS key | 获取所有字段名 |
| HLEN key | 获取字段数量 |
| HMSET key field value [field value...] | 批量设置字段与值 |
| HMGET key field [field...] | 批量获取字段值 |
| HSET key field value | 设置单个字段与值 |
| HSETNX key field value | 仅当字段不存在时设置值 |
| HVALS key | 获取所有字段值 |
| HSCAN key cursor | 迭代键值对,cursor 为游标 |
应用场景与内存优化建议
Hash 类型天然适合存储结构化对象(如用户信息、商品属性、配置项等)。相较于将整个对象序列化为 JSON 字符串存入 String 类型,Hash 具备以下优势:
- 局部更新:可直接修改单个 field,无需读取整个对象、反序列化、修改后再序列化写回。
- 内存紧凑:小对象使用 ziplist 存储时,内存开销远低于独立 String 键。
- 原子操作:支持
HINCRBY等原子增量操作,适合计数器场景。
在实际使用中,建议控制单个 Hash 的字段数量与字符串长度,使其尽量保持在 ziplist 阈值内(总长度 < 64 字节,数量 < 512),以充分利用紧凑存储带来的性能优势。对于超大 Hash,可考虑按业务维度拆分 key,避免单个键占用过多内存或引发阻塞。
在线练习工具:https://try.redis.io/
查看更多命令:https://redis.io/commands
