Redis 常见数据类型-Hash 类型

Redis 常见数据类型-Hash 类型

七月 08, 2025 次阅读

类型简介

⼏乎所有的主流编程语⾔都提供了哈希(hash)类型,它们的叫法可能是哈希、字典、关联数组、映射。在 Redis 中,哈希类型是指值本⾝
⼜是⼀个键值对结构,形如 key = “key”,value = { {field1, value1 }, …, {fieldN, valueN } },Redis 键值对和哈希类型⼆
者的关系可以⽤下面这张图来表⽰

flowchart TD
    subgraph String_Storage
        A[key: user:1:name] --> B[value: James]
        C[key: user:1:age] --> D[value: 28]
    end

    subgraph Hash_Storage
        E[key: user:1] --> F[field: name]
        F --> G[value: James]
        E --> H[field: age]
        H --> I[value: 28]
    end

相关指令

不同于以往的 set 和 get,Hash 会采用专门的指令来设置 Hash 或获取 Hash 中特定 field 所对应的 value

1、hset 指令

语法格式如下:

HSET key field value [field value ...]

和 String 类型一样,利用该命令可同时生成多组键值对(field-value),其中返回值对应的是成功添加的字段的个数

# 生成含有一个键值对的 hash 数据
127.0.0.1:6379> hset k1 f1 v1
(integer) 1
# 生成含有多个键值对的 hash 数据
127.0.0.1:6379> hset k2 f1 v1 f2 v2 f3 v3
(integer) 3

2、hget 指令

该指令用于获取 hash 中指定字段的值

语法格式如下:

HGET key field

返回值:若字段存在则返回字段所对应的值,否则返回 nil

使用示例如下:

# 查询 k1 中 f1 所对应的值
127.0.0.1:6379> hget k1 f1
"v1"
# 查询不存在的值(返回nil)
127.0.0.1:6379> hget k1 f2
(nil)
# 查询 k2 中 f3 所对应的值
127.0.0.1:6379> hget k2 f3
"v3"

3、hexists

该指令用于判断 hash 中某个字段是否存在

语法格式如下:

HEXISTS key field

返回值:1 表示存在,0 表示不存在

使用示例:

127.0.0.1:6379> hexists k1 f1
(integer) 1
127.0.0.1:6379> hexists k1 f2
(integer) 0

4、hdel

该指令用于删除 hash 中某个字段

返回值:本次操作删除的字段个数

语法格式如下:

HDEL key field [field ...]

若一个 hash 值中的所有字段都被删除了,那么其 hash 也会被删除

使用示例如下:

# 删除一个字段
127.0.0.1:6379> hdel k2 f2
(integer) 1
# 删除两个字段,因为 f2 字段刚刚已经被删掉了
127.0.0.1:6379> hdel k2 f1 f2 f3
(integer) 2
# k2 已经被删除
127.0.0.1:6379> exists k2
(integer) 0

注意:不要混淆 exists 和 hexists 以及 del 和 hdel,前者是用来操作 key 值的,而后者是用来操作 key 中指定字段的
示例如下:

# 之前的 k2 已经被删除,直接生成一个字符串类型 k2
127.0.0.1:6379> set k2 v2
OK
# exists 和 del 不管用户给的是什么数据类型,直接操控和访问的是 key
127.0.0.1:6379> exists k1 k2
(integer) 2
127.0.0.1:6379> del k1 k2
(integer) 2

5、hkeys、hvals、hgetall

  • hkeys 用于查询指定的 hash 中的所有字段
  • hvals 用于查询指定的 hash 中的所有字段所对应的值
  • hgetall 综合了 hkeys 和 hvals 两者,返回指定的 hash 中的所有字段及字段所对应的值

格式如下:

# hkeys
HKEYS key
# hvals
HVALS key
# hgetall
HGETALL key

使用示例如下:

127.0.0.1:6379> hset key f1 v1 f2 v2 f3 v3
(integer) 3
# hkeys
127.0.0.1:6379> hkeys key
1) "f1"
2) "f2"
3) "f3"
# hvals
127.0.0.1:6379> hvals key
1) "v1"
2) "v2"
3) "v3"
# hgetall
127.0.0.1:6379> hgetall key
1) "f1"
2) "v1"
3) "f2"
4) "v2"
5) "f3"
6) "v3"

6、hmget

相较于 hget,该指令可以一次获取多个值

格式如下:

HMGET key field [field ...]

用法和 hget 类似,下面是使用示例:

# 书接上回
127.0.0.1:6379> hmget key f1 f2 f3
1) "v1"
2) "v2"
3) "v3"

7、hlen

顾名思义,这个命令是用来获取 hash 中字段个数的,格式如下:

HLEN key

使用示例如下:

# 书接上回
127.0.0.1:6379> hlen key
(integer) 3

8、hsetnx

该命令用于在字段不存在的情况下,设置 hash 中的字段和值,格式如下:

HSETNX key field value

返回值:1 表示成功,0 表示失败

用法如下:

# 成功
127.0.0.1:6379> hsetnx k1 f1 v1
(integer) 1
# 成功
127.0.0.1:6379> hsetnx k1 f2 v2
(integer) 1
# 失败
127.0.0.1:6379> hsetnx k1 f2 v2
(integer) 0

9、hincrby、hincrbyfloat

hincrby 用于整数进行加减运算,而 hincrbyfloat 用于浮点数运算

返回值:计算之后的结果

格式如下:

# hincrby
HINCRBY key field increment
# hincrbyfloat
HINCRBYFLOAT key field increment

需要注意的是,hincrby 操作时 value 必须是 int 类型,但若通过 hincrbyfloat 对 int 类型进行操作后,
即便操作的是整数的加减,也会导致 value 变成非 int 类型

使用示例如下:

127.0.0.1:6379> hset k1 f1 5
(integer) 1
127.0.0.1:6379> hincrby k1 f1 6
(integer) 11
127.0.0.1:6379> hincrbyfloat k1 f1 6
"17"
127.0.0.1:6379> hincrbyfloat k1 f1 6.5
"23.5"
127.0.0.1:6379> hincrbyfloat k1 f1 3.3
"26.8"
127.0.0.1:6379> hincrbyfloat k1 f1 -4.4
"22.4"
# value 已不再是 int 类型,无法使用 hincrby 进行操作
127.0.0.1:6379> hincrby k1 f1 -6
(error) ERR hash value is not an integer

hash 相关指令汇总表

命令 执行效果 时间复杂度
hset key field value 设置值 O(1)
hget key field 获取值 O(1)
hdel key field [field …] 删除 field O(k), k 是 field 个数
hlen key 计算 field 个数 O(1)
hgetall key 获取所有的 field-value O(k), k 是 field 个数
hmget field [field …] 批量获取 field-value O(k), k 是 field 个数
hmset field value [field …] 批量设置 field-value O(k), k 是 field 个数
hexists key field 判断 field 是否存在 O(1)
hkeys key 获取所有的 field O(k), k 是 field 个数
hvals key 获取所有的 value O(k), k 是 field 个数
hsetnx key field value 设置值,但必须在 field 不存在时才能设置成功 O(1)
hincrby key field n 对应 field-value +n O(1)
hincrbyfloat key field n 对应 field-value +n O(1)
hstrlen key field 计算 value 的字符串长度 O(1)

内部编码

哈希的内部编码有两种:

  • ziplist(压缩列表):当哈希类型元素个数⼩于 hash-max-ziplist-entries 配置(默认 512 个)、同时所有值都
    ⼩于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使⽤ ziplist 作为哈希的内部实现,ziplist 使
    ⽤更加紧凑的结构实现多个元素的连续存储,所以在节省内存⽅⾯⽐ hashtable 更加优秀。

  • hashtable(哈希表):当哈希类型⽆法满⾜ ziplist 的条件时,Redis 会使⽤ hashtable 作为哈希
    的内部实现,因为此时 ziplist 的读写效率会下降,⽽ hashtable 的读写时间复杂度为 O(1)。

若哈希中字段数量过多或者 value 过长都会使得存储方式变为 hashtable,而具体数值是可以在 conf 文件中被配置的:

# Hashes are encoded using a memory efficient data structure when they have a
# small number of entries, and the biggest entry does not exceed a given
# threshold. These thresholds can be configured using the following directives.
hash-max-listpack-entries 512
hash-max-listpack-value 64

下面是不同长度的 value 存储方式的区别:

127.0.0.1:6379> hset k1 f1 v1
(integer) 1
127.0.0.1:6379> OBJECT ENCODING k1
"listpack"
127.0.0.1:6379> hset k1 f2 vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv22222222222222222222222222222222222222222222222222222222
(integer) 1
# value 太长,类型变为 hashtable
127.0.0.1:6379> OBJECT ENCODING k1
"hashtable"

使用场景:缓存

在关系型数据库当中,数据的存储是必须争对每一列属性都有对应的值的,而通过哈希表的方式存储,可以按需插入数据,且看起来更加直观,并且
更新起数据更加灵活。

二者各有各的好处,若使用关系型数据库进行数据的存储,虽然会消耗更多的空间,但相对于着可以进行更多复杂的操作,若使用哈希进行数据的操作,
操作可行性更低(能做的操作更加有限),当然,好处就是哈希所消耗的空间更少。因此,具体使用哪种方式得根据具体实际去讨论。当然,这两者并
不冲突,因为哈希是用作缓存的,而关系型数据库是直接用于存储的,很多时候是两者相结合着一起使用,对于复杂的表操作固然选择使用关系型数据库
进行操作,而对于一些简单数据操作,则选择直接对缓存进行操作,因此,我们对这两者的比较并不是为了分出胜负来,因为我们正是因为它们各有优劣
才选择将两者结合起来使用的,不要忘了我们的初衷,Redis 更多是为了帮助忙不过来的关系型数据库服务的。

关系型数据表保存用户信息:

uid name age city
1 James 28 Beijing
2 Johnathan 30 Xian

映射关系表示用户信息

key field value
user:1 uid 1
name James
age 28
city Beijing
user:2 uid 2
name Johnathan
age 30
city Xian

不同缓存方式之间的比较

下面我们需要讨论三种不同的缓存方式:

原生字符串类型⸺使⽤字符串类型,每个属性⼀个键

set user:1:name James
set user:1:age 23
set user:1:city Beijing

优点:实现简单,针对个别属性变更也很灵活。
缺点:占⽤过多的键,内存占⽤量较⼤,同时⽤⼾信息在 Redis 中⽐较分散,缺少内聚性,所以这种
⽅案基本没有实⽤性。

序列化字符串类型,例如 JSON 格式

set user:1 经过序列化后的⽤⼾对象字符串

优点:针对总是以整体作为操作的信息⽐较合适,编程也简单。同时,如果序列化⽅案选择合适,内
存的使⽤效率很⾼。
缺点:本⾝序列化和反序列需要⼀定开销,同时如果总是操作个别属性则⾮常不灵活。

哈希类型

hmset user:1 name James age 23 city Beijin

优点:简单、直观、灵活。尤其是针对信息的局部变更或者获取操作。
缺点:需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,可能会造成内存的较⼤消耗。

相⽐于使⽤ JSON 格式的字符串缓存⽤⼾信息,哈希类型变得更加直观,并且在更新操作上变得更灵活。可以将每个⽤⼾的 id 定义
为键后缀,多对 field-value 对应⽤⼾的各个属性,类似如下伪代码

/**
 * 根据用户ID获取用户信息(缓存优先)
 * @param uid 用户ID
 * @return 用户信息对象,未找到时返回null
 */
UserInfo getUserInfo(long uid) {
    // 1. 构建Redis键
    String key = "user:" + uid;

    // 2. 尝试从Redis获取缓存数据
    Map<String, String> userInfoMap = Redis执行命令:hgetall key;

    // 3. 缓存命中处理
    if (userInfoMap != null && !userInfoMap.isEmpty()) {
        // 将Redis哈希数据转换为UserInfo对象
        UserInfo userInfo = 利用映射关系构建对象(userInfoMap);
        return userInfo;
    }

    // 4. 缓存未命中,查询数据库
    UserInfo userInfo = MySQL执行SQL:select * from user_info where uid = <uid>;

    // 5. 处理未找到用户的情况
    if (userInfo == null) {
        // 记录日志或响应404
        return null;
    }

    // 6. 写入Redis缓存
    Redis执行命令:hmset key 
        name userInfo.name 
        age userInfo.age 
        city userInfo.city;

    // 7. 设置缓存过期时间(1小时)
    Redis执行命令:expire key 3600;

    // 8. 返回用户信息
    return userInfo;
}