一、Redis中哈希Hashes数据类型简介
1.1 哈希(Hashes)简介
Redis 的 哈希表(Hashes) 是一个 Strings 类型的 field 和 value 的映射表,Hashes 特别适合用于存储对象、映射关系和键值对等数据。Redis 中每个 Hashes 可以存储 2^32^-1 键值对(40 多亿)。Redis 的哈希底层结构和传统的哈希结构一样,由数组和链表组成。
哈希表中的每一个 key 都是独一无二的,程序可以根据 key 查找到与之关联的 value,或者通过 key 来更新 value,又或者根据 key 来删除整个 key-value等等。
哈希表优点在于,它能以 O(1) 的复杂度快速查询数据。将 key 通过 Hash 函数的计算,就能定位数据在表中的位置,因为哈希表实际上是数组,所以可以通过索引值快速查询到数据。
在哈希表大小固定的情况下,随着数据不断增多,那么哈希冲突的可能性也会越高。Redis 采用了「链式哈希」来解决哈希冲突,在不扩容哈希表的前提下,将具有相同哈希值的数据串起来,形成链接起,以便这些数据在表中仍然可以被查询到。
1.2 Hashes 类型内部编码及底层结构介绍
Redis 的哈希表结构如下:
|
|
哈希表由 dictht 结构定义:
- table 属性是一个数组, 数组中的每个元素都是一个指向 dictEntry 结构的指针。
- size 属性记录了哈希表的大小
- used 属性则记录了哈希表目前已有节点(键值对)的数量。
- sizemask 属性的值总是等于 size - 1 , 这个属性和哈希值一起决定一个键应该被放到 table 数组的哪个索引上面。
哈希表是一个 数组(dictEntry table) ,数组的每个元素是一个指向 哈希表节点(dictEntry)
的指针。
哈希表节点 dictEntry
的结构如下:
|
|
哈希表节点使用 dictEntry
结构表示, 每个 dictEntry
结构都保存着一个键值对:
dictEntry
结构里不仅包含指向键和值的指针,还包含了指向下一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对链接起来,以此来解决哈希冲突的问题,这就是链式哈希。
dictEntry
结构里键值对中的值是一个 联合体 union v
,键值对中的值可以是一个指向实际值的指针,或者是一个无符号的 64 位整数或有符号的 64 位整数或double 类的值。这么做的好处是可以节省内存空间,因为当 值 是整数或浮点数时,就可以将值的数据内嵌在 dictEntry 结构里,无需再用一个指针指向实际的值,从而节省了内存空间。
字典结构定义:
|
|
字典由 dict 结构定义:
- type 属性是一个指向 dictType 结构的指针,每个 dictType 结构保存了一簇用于操作特定类型键值对的函数,Redis 会为用途不同的字典设置不同的类型特定函数。
- privdata 属性则保存了需要传给那些类型特定函数的可选参数。
type 属性和 privdata 属性是针对不同类型的键值对,为创建多态字典而设置的.
|
|
- ht 属性是一个包含两个项的数组, 数组中的每个项都是一个 dictht 哈希表,
Tips: 一般情况下, 字典只使用 ht[0] 哈希表, ht[1] 哈希表只会在对 ht[0] 哈希表进行 rehash 时使用。
- rehashidx 属性是另一个和 rehash 有关的属性,它记录了 rehash 目前的进度, 如果目前没有在进行 rehash , 那么它的值为 -1 。 普通状态下字典:
Redis 采用了 链式哈希 的方法来解决哈希冲突。实现的方式就是每个哈希表节点都有一个 next 指针,用于指向下一个哈希表节点,因此多个哈希表节点可以用 next 指针构成一个单项链表,被分配到同一个哈希桶上的多个节点可以用这个单项链表连接起来,这样就解决了哈希冲突。
在 Redis 中,哈希数据类型有两种内部编码方式,分别是 ziplist(压缩列表) 和 hashtable(哈希表)。这两种编码方式的选择取决于哈希的大小和存储特性。
1、ziplist(压缩列表) 是 Redis 中用于内部编码较小哈希的紧凑数据结构。以下是一些关于 ziplist 的关键特性:
- 当哈希类型的元素个数相对较少,且所有字段和对应的值都满足一定的限制条件时,Redis 会使用 ziplist 作为哈希的内部实现。
- 默认情况下,Redis 会选择 ziplist。具体来说,如果哈希的元素(key-value对)个数不超过 512 个,并且所有值都小于 64 字节,那么 ziplist 就是首选的编码方式。
- Ziplist 是一种紧凑的数据结构,它能够在节省内存方面表现得比 hashtable 更出色。它将多个哈希元素连续存储在一起,有效地减少了内存占用。
ziplist(压缩列表) 分布结构如下:
|
|
2、hashtable(哈希表) 是 Redis 中用于存储大规模哈希数据的内部编码方式。以下是 hashtable 的关键特性:
- 当哈希类型的元素个数超过了 ziplist 的配置限制,或者有字段对应的值大于 64 字节时,Redis 会将内部编码切换为 hashtable。
- Hashtable 是一种散列表数据结构,它具有 O(1) 的读写时间复杂度,适用于大规模的哈希数据集。
- 切换到 hashtable 可以提供更好的性能和内存管理,特别是在处理大型哈希或包含大值的情况下。
hashtable(哈希表) 针对指定的 key 进行散列计算后,可以映射到数组的一个位置,然后在指定的索引位置获取或存放数据,如果指定位置上存在数据(冲突),则进行链表遍历或添加链表,结构示图如下:
二、Hashes类型相关命令
2.1 Redis Hash 相关命令简介
以下是关于 Redis Hash 相关命令的简介表,包括命令、作用以及时间复杂度:
命令 | 作用 | 时间复杂度 |
---|---|---|
HSET | 在哈希表中设置字段的值,存在则更新,否则创建。可同时设置多组字段。 | O(1) |
HSETNX | 仅在字段不存在时,在哈希表中设置字段的值,成功返回1,否则返回0。 | O(1) |
HMSET | 同时将多个 field-value(域-值)对设置到哈希表 key 中。 | O(N),N 为 field-value 对的数量。 |
HGET | 获取指定哈希表中字段的值。 | O(1) |
HMGET | 获取指定哈希表中多个字段的值。 | O(N),N为字段数 |
HSCAN | 命令用于迭代哈希键中的键值对。 | O(N) |
HKEYS | 获取指定哈希表中所有字段的名称。 | O(N),N为字段数 |
HVALS | 获取指定哈希表中所有字段的值。 | O(N),N为字段数 |
HGETALL | 获取指定哈希表中所有字段和对应的值。 | O(N),N为字段数 |
HEXISTS | 检查指定哈希表中是否存在某个字段。 | O(1) |
HDEL | 删除指定哈希表中的一个或多个字段。 | O(N),N为被删除的字段数 |
HLEN | 获取指定哈希表中字段的数量(即哈希表的大小)。 | O(1) |
HSTRLEN | 返回哈希表 key 中,与指定域 field 相关联的值的字符串长度。 | O(1) |
HINCRBY | 将哈希表中指定字段的值增加一个整数。 | O(1) |
HINCRBYFLOAT | 将哈希表中指定字段的值增加一个浮点数。 | O(1) |
注意:时间复杂度中的 “N” 表示操作的复杂度与哈希表的长度或插入元素的数量成线性关系,而不是固定的常数时间。在实际使用中,需要根据数据规模和性能要求选择适当的命令。
2.2 HSET、HSETNX 和 HMSET 命令
1、HSET 命令 HSET 命令的作用是向指定的哈希表(key)中设置字段的值;如果 key 不存在,一个新的哈希表(key)被创建并进行 HSET 操作;如果字段存在则更新,否则创建;可以同时设置多组字段; 命令语法:
|
|
命令返回值:
- 如果 key 不存在,一个新的哈希表(key)被创建并进行 HSET 操作, 返回设置的 field 字段数;
- 如果 field 列表中有 N个 是哈希表中的新建 field,并且值设置成功,返回 N;
- 如果哈希表中域 field 全部 已经存在且旧值已被新值覆盖,返回 0;
- 当 key 存在,但不是哈希表类型时,返回类型不匹配的错误信息;
2、HSETNX 命令 HSETNX 命令的作用是仅在字段不存在时,在指定的哈希表中设置字段的值。设置成功返回 1,否则返回 0;一次仅可设置一个field; 命令语法:
|
|
HSETNX 命令返回值有 2 种情况:
- 如果 key 不存在,一个新哈希表被创建并执行 HSETNX 命令。
- 若设置成功,返回 1;
- 如果指定field已经存在且没有操作被执行,返回 0
- 当 key 存在,但不是哈希表类型时,返回类型不匹配的错误信息;
3、HMSET 命令 HMSET 命令同时将多个 field-value(域-值)对设置到哈希表 key 中,HMSET 会覆盖哈希表中已存在的域,如果 key 不存在,一个空哈希表被创建并执行 HMSET 操作。 命令语法:
|
|
命令返回值:
- 如果 key 不存在,一个新的哈希表(key)被创建并进行 HMSET 操作,返回 OK;
- 如果哈希表 key 存在,field 存在的 value 会被覆盖,不存在的会被添加,返回 OK;
- 当 key 存在,但不是哈希表类型时,返回类型不匹配的错误信息;
2.3 HGET、HMGET、HGETALL 和 HSCAN 命令
1、HGET 命令 HGET 命令的作用是获取指定哈希表(key)中指定字段(field)的值,一次只能获取一个field; 命令语法:
|
|
HGET 命令返回有 4 种情况:
- 当指定的哈希表 key 不存在时,返回 nil;
- 当指定的哈希表 key 存在时,并且指定的field 也存在,返回field 的值;
- 当指定的哈希表 key 存在时,并且指定的field 不存在,返回 nil;
- 当指定的 key 不是哈希表时,返回类型不匹配的错误信息;
2、HMGET 命令 HMGET 命令返回哈希表 key 中,一个或多个指定 field 的值,指定多个 field 的关联值的列表排列顺序和指定 field 参数的请求顺序一样。 命令语法:
|
|
HMGET 命令返回有 3 种情况:
- 当指定的哈希表 key 不存在时,返回 与 field 数量相同的 nil(列表);
- 当指定的哈希表 key 存在时,返回 与 field 位置对应的 value 列表,如果指定的 field 不存在,对应位置为 nil ;
- 当指定的 key 不是哈希表时,返回类型不匹配的错误信息;
3、HGETALL 命令 HGETALL 命令的作用是获取哈希表 key 中,所有的 field 和 value值,在返回值里,每 field 之后是其对应的 value 值,所以返回值的长度是哈希表大小的两倍。 命令语法:
|
|
HGETALL 命令返回有 3 种情况:
- 当指定的哈希表 key 不存在时,返回 (empty array);
- 当指定的哈希表 key 存在时,以列表形式返回哈希表所有的 field 和 field对应的 value 值,在返回值里,每 field 之后是其对应的 value 值,然后是下一个field 及 下一个field对应的值,依次类推,直至返回hash 表 的全部field-value 对;
- 当指定的 key 不是哈希表时,返回类型不匹配的错误信息;
4、HSCAN 命令 HSCAN 命令用于迭代哈希键中的键值对。 命令语法:
|
|
cursor 游标位置,游标参数被设置为0时,服务器将开始一次新的迭代。
2.4 HKEYS 和 HVALS 命令
1、HKEYS 命令 HKEYS 命令的作用是获取指定哈希表中所有字段的名称。 命令语法:
|
|
HKEYS 命令返回有 3 种情况:
- 当指定的哈希表 key 不存在时,返回 (empty array);
- 当指定的哈希表 key 存在时,返回一个包含哈希表中所有 fields 的列表;
- 当指定的 key 不是哈希表时,返回类型不匹配的错误信息;
2、HVALS 命令 HVALS 命令的作用是获取指定哈希表中所有字段的value值。 命令语法:
|
|
HVALS 命令返回有 3 种情况:
- 当指定的哈希表 key 不存在时,返回 (empty array);
- 当指定的哈希表 key 存在时,命令返回一个包含哈希表中所有 fields 的值 的列表;
- 当指定的 key 不是哈希表时,返回类型不匹配的错误信息;
2.5 HEXISTS、HLEN、HSTRLEN 和 HDEL 命令
HEXISTS 命令 HEXISTS 命令的作用是查看哈希表 key 中,指定的 field 是否存在。 命令语法:
|
|
HEXISTS 命令返回有 3 种情况:
- 当指定的哈希表 key 不存在 或 哈希表中指定的 field 不存在时,返回 0;
- 当哈希表中指定的 field 存在,返回 1;
- 当指定的 key 不是哈希表时,返回类型不匹配的错误信息;
2、HLEN 命令 HLEN 命令返回哈希表 key 中 field 的数量,当散列有可能包含的值非常大,那么用户可以先使用 HLEN 命令获取出散列包含的域的数量,然后再结合 HKEYS 命令列出所有域名,再进一步可以使用 HGET 一个接一个地取出域的值,从而避免因为一次获取多个大体积的值而导致服务器阻塞。 命令语法:
|
|
HLEN 命令返回 3 种情况:
- 当指定的 key 存在时,返回哈希表中域的数量;
- 当指定的 key 不存在时,返回 0;
- 当指定的 key 不是哈希类型时,返回类型不匹配的错误信息;
3、HSTRLEN 命令 HSTRLEN 命令返回哈希表 key 中,与指定域 field 相关联的值的字符串长度。 命令语法:
|
|
HSTRLEN 命令返回值有 2 种情况:
- 如果指定的 key 或者 filed 不存在时,那么命令返回 0;
- 如果指定的 key 且 filed 存在时,返回字符串的长度;
- 当指定的 key 不是哈希类型时,返回类型不匹配的错误信息;
4、HDEL 命令 HDEL 命令删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。2.4 版本之前,HDEL 命令每次只能删除单个指定域;在 2.4 版本之前,如果需要在一个原子时间内删除多个域,需将命令包含在 MULTI/EXEC 块内;在 2.4 版本及之后,HDEL 命令才支持一次删除多个域操作。 命令语法:
|
|
命令参数
- key:哈希表指定的键。
- filed:哈希表中指定的域。
HDEL 命令返回有 3 种情况:
- 当指定的哈希表 key 不存在 或 哈希表key 存在 但 指定的 fields 不存在时,返回 0;
- 当哈希表中指定的 field 部分或全部存在,返回被成功移除的 field(存在的field)的数量,不存在的 field 被忽略;
- 当指定的 key 不是哈希表时,返回类型不匹配的错误信息;
2.6 HINCRBY 和 HINCRBYFLOAT 命令
1、HINCRBY 命令 HINCRBY 命令为哈希表 key 中的域 field 的值加上增量 increment。增量也可以为负数,相当于对指定域进行减法操作。 命令语法:
|
|
如果 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。如果 field 不存在,那么在执行命令前,域的值被初始化为 0。
对一个储存字符串值的域 field 执行 HINCRBY 命令将造成一个错误。
HINCRBY 操作的值被限制在 64 位有符号数字表示之内。
命令返回值:
- 如果 key 不存在则创建 哈希表 key 并执行 HINCRBY 命令,field的值被初始化为 0,然后再加上 increment,返回修改后的值 increment;
- 如果 哈希表 key 存在,field 不存在,那么在执行 HINCRBY 命令前,field的值被初始化为 0,然后再加上 increment,返回修改后的值 increment;
- 如果 哈希表 key 存在,field 对应的 value 为整数,则value值 加上 increment 后,返回修改后的值 value + increment;
- 如果 哈希表 key 存在,field 对应的 value 不为整数(为字符串、或浮点数deng),返回错误提示信息
(error) ERR hash value is not an integer
- 当指定的 key 不是哈希表时,返回类型不匹配的错误信息;
2、HINCRBYFLOAT 命令 HINCRBYFLOAT 命令为哈希表 key 中的域 field 的值加上浮点数增量 increment。增量也可以为负浮点数,相当于对指定域进行减法操作。
HINCRBYFLOAT 命令的详细功能和 INCRBYFLOAT 命令类似。
命令语法:
|
|
如果键 key 不存在,那么 HINCRBYFLOAT 会先创建一个哈希表 key,再创建域 field 并将域 field 的值设为 0,最后再执行加法操作;如果哈希表 key 中没有域 field,那么 HINCRBYFLOAT 会先将域 field 的值设为 0,然后再执行加法操作。
命令返回值:
- 如果 key 不存在则创建 哈希表 key 并执行 HINCRBYFLOAT 命令,field的值被初始化为 0,然后再加上 increment,返回修改后的值 increment;
- 如果 哈希表 key 存在,field 不存在,那么在执行 HINCRBY 命令前,field的值被初始化为 0,然后再加上 increment,返回修改后的值 increment;
- 如果 哈希表 key 存在,field 对应的 value 为整数或浮点数,则value值 加上 increment 后,返回修改后的值 value + increment;
- 如果 哈希表 key 存在,field 对应的 value 不为整数或浮点数(为字符串等),返回错误提示信息
(error) ERR hash value is not an integer
- 当指定的 key 不是哈希表时,返回类型不匹配的错误信息;
三、Hashes 类型的应用场景
3.1 哈希类型用于对象数据存储场景
举例:用户信息、商品信息、文章信息、购物车信息。