Redis数据结构之散列
前言
在上一章《Redis学习手册1—数据结构之字符串》中,我们学习了Redis最基本的数据结构——字符串,但是我们发现,在使用字符串类型键存储具有关联关系的一系列属性时,我们不得不创建若干字符串键,分别存储不同的属性。例如,我们想要使用字符串描述一篇文章的属性:标题、内容、作者和创建时间。如果使用字符串类型键存储这些属性,我们至少需要使用四个字符串类型键来存储,然后定义相同的键的前缀,如:article::1::title、article::1::content、article::1::author、article::1::create_time。
虽然在技术上是可行的,但是要知道键本身也是数据,也是需要占用内存资源的,那么当数据量大的时候,就会造成不必要的内存浪费。为了解决这个问题,使得相关联的数据能够被打包起来存储,Redis提供了散列类型(hash) 键。
散列简介
Redis的 散列(hash) 键会将一个键和一个散列在数据库里关联起来,用户可以在散列中为任意多个 字段(field) 设置值。与字符串一样,散列的字段和值既可以是文本数据,也可以是二进制数据。
通过使用散列键,用户可以把相关联的多项数据存储到同一个散列里,以便对这些数据进行管理,或者针对它们执行批量操作。
散列存储结构
以文章数据为例,下图展示了散列键在内存中的存储结构:
散列键的功能
Redis为散列键提供了一系列操作命令,通过使用这些命令,用户可以:
- 为散列的字段设置值,或者只在字段不存在的情况下为它设置值;
- 从散列里面获取给定字段名的值;
- 对存储着数字值的字段执行加法或减法操作;
- 检查给定的字段是否存在于散列中;
- 从散列中删除指定的字段;
- 查看散列包含的字段数量;
- 一次为散列的多个字段设置值,或者一次从散列中获取多个字段的值;
- 获取散列包含的所有字段、所有值或所有字段和值;
散列键命令速查表
以下表格中,列举了散列键提供的所有命令及基本使用说明:
命令 | 用法及参数 | 说明 |
---|---|---|
HSET | HSET hash field value | 为散列中指定的字段设置值 |
HSETNX | HSETNX hash field value | 只在指定字段不存在时设置值 |
HGET | HGET hash field | 获取指定字段的值 |
HINCRBY | HINCRBY hash field increment | 为给定的字段值执行加法运算,与字符串键的INCRBY 命令用法一致 |
HINCRBYFLOAT | HINCRBYFLOAT hash field increment | 支持浮点数字段值的加法操作 |
HSTRLEN | HSTRLEN hash field | 获取给定字段值的字节长度 |
HEXISTS | HEXISTS hash field | 判断给定的字段是否存在于散列中 |
HDEL | HDEL hash field | 删除指定字段及其关联的值 |
HMSET | HMSET hash field value [field value ...] | 一次为多个字段设置值 |
HMGET | HMGET hash field [field ...] | 一次获取多个字段的值 |
HKEYS | HKEYS hash | 获取散列中包含的所有字段 |
HVALS | HVALS hash | 获取散列中包含的所有值 |
HGETALL | HGETALL hash | 获取散列中包含的所有字段和值 |
命令详解
HSET命令:为字段设置值
用户可以通过执行HSET命令为散列中指定的字段设置值:
HSET hash field value
根据给定的字段是否已经存在于散列中,HSET命令的行为也会有所不同:
- 如果给定的字段不存在于散列中,那么这次设置就是一次创建操作,命令将在散列中关联起给定的字段和值,然后返回 1;
- 如果给定的字段已经存在于散列中,那么这次设置就是一次更新操作,命令将使用用户给定的新值去覆盖原来的值,然后返回 0。
127.0.0.1:6379> HSET article::1 title "greeting"
(integer) 1
127.0.0.1:6379> HSET article::1 content "hello world"
(integer) 1
127.0.0.1:6379> HSET article::1 author "peter"
(integer) 1
127.0.0.1:6379> HSET article::1 create_time "2020-06-03 12:52:20"
(integer) 1
127.0.0.1:6379> HSET article::1 title "Redis Tutorial"
(integer) 0
127.0.0.1:6379> HSET article::1 content "Redis is a data structure store ..."
(integer) 0
复杂度:
O
(
1
)
O(1)
O(1)
版本要求:HSET命令从Redis 2.0.0版本开始可用。
HSETNX命令:只在字段不存在时为它设置值
HSETNX命令的作用和HSET命令的作用非常相似,区别在于,HSETNX命令只会在指定字段不存在时执行设置操作:
HSETNX hash field value
HSETNX命令在字段不存在并且成功设置值时返回 1,在字段已存在并导致设置操作失败时返回0。
127.0.0.1:6379> HSETNX article::1 title "Redis Performance Test"
(integer) 0 -- 设置成功
127.0.0.1:6379> HSETNX article::1 view_count 100
(integer) 1
复杂度:
O
(
1
)
O(1)
O(1)
版本要求:HSETNX命令从Redis 2.0.0版本开始可用。
HGET命令:获取字段的值
HGET命令可以根据用户给定的字段,从散列中获取该字段的值:
HGET hash field
127.0.0.1:6379> HGET article::1 title
"greeting"
127.0.0.1:6379> HGET article::1 author
"peter"
当给定的字段或散列不存在时,那么HGET命令将返回一个空值:
127.0.0.1:6379> HGET account::54321 location
(nil)
复杂度:
O
(
1
)
O(1)
O(1)
版本要求:HGET命令从Redis 2.0.0版本可用。
HINCRBY命令
与字符串键的INCRBY命令一样,如果散列的字段里面存储着能够被Redis解释为整数的数字,那么用户就可以使用HINCRBY命令为该字段的值上加上指定的整数增量:
HINCRBY hash field increment
HINCRBY命令在成功执行加法操作之后将返回字段当前的值作为命令的结果。
127.0.0.1:6379> HINCRBY article::1 view_count 1
(integer) 1
因为Redis只为散列提供了加法操作的HINCRBY命令,但是没有为散列提供相应的减法操作命令,因此可以使用传入负数增量给HINCRBY命令,即可执行减法操作:
127.0.0.1:6379> HSET hash article::1 view_counte 100
(integer) 1
127.0.0.1:6379> HINCRBY hash article::1 view_counte 50
(integer) 50
HINCRBY命令只能作用于字段值为整数,且增量也只能为整数,否则Redis会返回错误。
复杂度:
O
(
1
)
O(1)
O(1)
版本要求:HNICRBY命令从Redis 2.0.0版本开始可用。
HINCRBYFLOAT命令
HINCRBYFLOAT命令和HINCRBY命令的作用类似,它们之间的主要区别在于HINCRBYFLOAT命令不仅可以使用整数作为增量,还可以使用浮点数作为增量:
HINCRBYFLOAT hash field increment
HINCRBYFLOAT命令在执行成功后,将返回给定字段的当前值作为结果。
127.0.0.1:6379> HGET fruit::apple price
"5.60"
127.0.0.1:6379> HINCRBYFLOAT fruit::apple price 1.4
"7"
如果加法计算的结果能够被表示为整数,那么HINCRBYFLOAT命令将使用整数作为计算结果。
同样的,Redis也没有提供与HINCRBYFLOAT命令对应的减法操作命令,因此,可以通过传入负数增量的方式来执行减法操作。
复杂度:
O
(
1
)
O(1)
O(1)
版本要求:HINCRBYFLOAT命令从Redis 2.0.0版本开始可用。
HSTRLEN命令
用户可以使用HSTRLEN命令获取给定字段值的字节长度:
HSTRLEN hash field
127.0.0.1:6379> HSET article::10086 title "hello world"
(integer) 1
127.0.0.1:6379> HSTRLEN article::10086 title
(integer) 11
如果给定的字段或散列不存在,那么HSTRLEN命令将返回0
复杂度:
O
(
1
)
O(1)
O(1)
版本要求:HSTRLEN命令从Redis 3.2.0版本开始可用。
HEXISTS命令
HEXISTS命令可用于检查用户给定的字段是否存在于散列中:
HEXISTS hash field
如果散列中包含给定的字段,那么命令返回1;如果给定的字段或散列不存在,则返回 0。
127.0.0.1:6379> HEXISTS hash not-exists-field
(integer) 0
127.0.0.1:6379> HEXISTS not-exists-hash not-exists-field
(integer) 0
127.0.0.1:6379> HEXISTS exists-hash exists-field
(integer) 1
复杂度:
O
(
1
)
O(1)
O(1)
版本要求:HEXISTS命令从Redis 2.0.0版本开始可用。
HDEL命令
HDEL命令用于删除散列中的指定字段及其相关联的值:
HDEL hash field
HDEL命令删除执行成功后返回1;如果给定的字段或给定的散列不存在,那么返回 0
127.0.0.1:6379> HDEL article::1 author
(integer) 1
127.0.0.1:6379> HDEL not-exists-hash not-exists-field
(integer) 0
复杂度:
O
(
1
)
O(1)
O(1)
版本要求:HDEL命令从Redis 2.0.0版本开始可用。
HLEN命令
HLEN命令可以获取散列中包含的字段数量:
HLEN hash
127.0.0.1:6379> HLEN article::1
(integer) 4
如果给定的散列不存在,那么返回0
复杂度:
O
(
1
)
O(1)
O(1)
版本要求:HLEN命令从Redis 2.0.0版本开始可用。
HMSET命令
用户可以使用HMSET命令一次为散列中的多个字段设置值:
HMSET hash field value [field value]
HMSET命令在设置成功时返回OK。
127.0.0.1:6379> HMSET article::10086 title "greeting" content "hello world" author "peter"
OK
如果使用HSET命令执行上述操作,那么需要两次才能完成,而使用HMSET命令却只需要一次就可以,因此可以减少与Redis服务器的通信次数,提高执行速度。
与HSET命令一样,如果指定的字段已存在于散列中,那么就使用新值去覆盖旧值。
复杂度:
O
(
N
)
O(N)
O(N),其中
N
N
N为被设置的字段数量。
版本要求:HMSET命令从Redis 2.0.0版本开始可用。
HMGET命令
与HMSET命令相对应的,HMGET命令可以一次返回多个字段的值:
HMGET hash field [field ...]
HMGET命令将按照用户给定的字段的顺序依次返回与之对应的值。
127.0.0.1:6379> HMGET article::10086 title content
1) "greeting"
2) "hello world"
与HGET命令一样,如果给定的字段或散列不存在,那么将返回空值 **(nil)**作为结果。
复杂度:
O
(
N
)
O(N)
O(N),其中
N
N
N为用户给定的字段数量。
版本要求:HMGET命令从Redis 2.0.0版本开始可用。
HKEYS、HVALS、HGETALL命令
Redis为散列提供了HKEYS、HVALS和HGETALL这3个命令,可以分别用于获取散列包含的所有字段、所有值以及所有字段和值:
HKEYS hash
HVALS hash
HGETALL hash
127.0.0.1:6379> HKEYS article::10086
1) "title"
2) "content"
127.0.0.1:6379> HVALS article::10086
1) "greeting"
2) "hello world"
127.0.0.1:6379> HGETALL article::10086
1) "title" -- 字段
2) "greeting" -- 字段的值
3) "content"
4) "hello world"
如果给定的散列并不存在,那么HKEYS、HVALS和HGETALL都将返回一个空列表。
Redis散列包含的字段在底层是以无序的方式存储的,根据字段插入的顺序不同,包含相同字段的散列在执行HKEYS、HVALS、HGETALL命令时可能会得到不同的结果,因此在使用这3个命令时,不应该对它们的结果顺序做任何假设。
复杂度:HKEYS命令、HVALS命令和HGETALL命令的复杂度都为 O ( N ) O(N) O(N),其中 N N N为散列包含的字段数量。
版本要求:HKEYS命令、HVALS命令和HGETALL命令都从Redis 2.0.0版本开始可用。
散列与字符串
类似的命令
以下表格展示了字符串命令与类似的散列命令:
字符串 | 散列 |
---|---|
SET——为一个字符串键设置值 | HSET——为散列的给定字段设置 |
SETNX——仅在字符串键不存在的时为它设置值 | HSETNX——仅在散列不包含指定字段时设置值 |
GET——获取字符串的值 | HGET——获取散列指定字段的值 |
STRLEN——获取字符串值的字节长度 | HSTRLEN——获取给定字段值的字节长度 |
INCRBY——对字符串键的值执行整数加法操作 | HINCRBY——对字段存储的数字值执行整数加法操作 |
INCRBYFLOAT——对字符串键存储的浮点数执行浮点数加法操作 | HINCRBYFLOAT——对字段存储的数字值执行浮点数加法操作 |
MSET——一次为多个字符串键设置值 | HMSET——一次为多个字段设置值 |
MGET——一次获取多个字符串键的值 | HMGET——一次获取多个字段的值 |
EXISTS——检查给定的键是否存在于数据库中,可用于所有类型的键 | HEXISTS——检查给定的字段是否存在于散列中 |
DEL——从数据库中删除指定的键,适用于所有类型的键 | HDEL——从散列中删除指定的字段及它的值 |
散列键的优点
散列键的最大优点,就是它只要在数据库中创建一个键,就可以把任意多个字段和值存储到散列里。相反,字符串键只能存储一个键值对。
字符串键的优点
虽然使用散列键可以有效的节约资源并更好的组织数据,但是字符串键也有自己的优点:
- 虽然散列键命令和字符串键命令在部分功能上有重合的地方,但是字符串键命令提供的操作比散列键命令更丰富。比如,字符串能够使用SETRANGE命令和GETRANGE命令设置或读取字符串值的部分内容,或者使用APPEND命令进行内容的追加,而散列键并不支持这些操作。
- 键的过期,因为Redis的键过期功能是针对整个键的,而散列键中包含的多个字段和值,却不能为各个字段设置不同的过期时间。
字符串键和散列键的选择
从资源占用、支持的操作及过期时间3个方面对比字符串和散列键的优缺点:
结合上述对比结果,总结了一些选择的条件和方法:
- 如果程序需要为每个数据项单独设置过期时间,那么使用字符串键;
- 如果程序需要对数据项执行诸如SETRANGE、GETRANGE或者APPEND等操作,那么优先考虑使用字符串键;
- 如果程序需要存储的数据项比较多,并且你希望尽可能的减少内存占用,优先使用散列键;
- 如果多个数据项在逻辑上属于同一组或者同一类,那么优先使用散列键;