
五种数据结构、内部编码
更新: 2025/2/24 字数: 0 字 时长: 0 分钟
Redis有5种数据结构分别是: string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)。
字符串
字符串类型是Redis最基础的数据结构,其他几种数据结构都是在字符串类型基础上构建的。
字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。
添加、获取、删除
这里的操作命令和前面的一模一样:
添加:添加一个键值对。
set 键 值
设置键为hello,值为world的键值对,返回结果为OK代表设置成功:
获取:获取一个键值对的值。
get 键
获取键hello的值,如果要获取的键不存在,则返回nil(空):
删除:删除键,返回成功删除键的个数。
del 键 [键 键 ...]
删除键hello的值,如果要删除的键不存在,则返回0:
批量添加、批量获取
批量添加:批量添加键值对。
mset 键 值 [键 值 ...]
通过mset命令一次性设置4个键值对:
批量获取:批量获取键值。
mget 键 [键 ...]
批量获取了键a、b、c、d、f的值,f键不存在,那么它的值为nil(空):
批量操作命令可以有效提高开发效率,假如没有批量操作命令,执行n次get命令需要按下面方式来执行:
使用批量操作命令后,要执行n次get命令操作只需要按照下面方式来完成:
假设网络时间为1毫秒,命令时间为0.1毫秒(按照每秒处理1万条命令算),那么执行1000次get命令和1次mget命令的区别如下:
长度、切片、替换、追加
长度:获取键值长度。
strlen 键
例如,设置键hello值为redisworld,使用 strlen
命令返回值为10:
?> 提示:在Redis中每个汉字的长度为3。
**切片:**开始下标从0开始计算,前闭后闭区间。
getrange 键 开始下标 结束下标
取键hello值中的前两个字符:
替换:替换指定位置的字符。
setrange 键 下标 替换后的字符
将hello键中的下标为0的字符替换为 p
字符:
追加: 向字符串尾部追加字符串。
append 键 字符串
向键hello值后面追加 vip
字符串:
时间复杂度、使用场景
Redis典型的使用场景是用作缓存:Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
列表
列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
**列表(list
)类型是用来存储多个有序的字符串。列表中的每个字符串称为元素(element
),一个列表最多可以存储2^32-1个元素。**在Redis中,可以对列表两端插入(push
)和弹出(pop
),还可以获取指定范围的元素列表、获取指定索引下标的元素等。
列表类型有两个特点:
- 列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。
- 列表中的元素可以是重复的。
添加、长度、获取
右添加:从右边插入一个或多个元素,返回列表长度。
rpush 键 值 [值 ...]
在 keylist
列表最右边依次插入元素c、b、a:
左添加:从左边插入一个或多个元素,返回列表长度。
lpush 键 值 [值 ...]
在 keylist
列表最左边依次插入元素1、2、3:
指定添加:在指定元素的前(before)或后(after)插入一个新的元素。
linsert 键 before|after 指定元素 添加元素
在列表中的元素b后面插入python:
长度:计算列表长度。
llen key
当前 keylist
列表长度为7:
获取:通过指定下标获取列表中的元素。
- 下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。
lindex 键 下标
获取列表最后一个元素:
修改、切片、弹出、删除
修改:通过指定下标修改列表中元素的值。
- 索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。
lset 键 下标值 新值
假切片:获取指定范围内的元素列表,原列表不发生改变。
lrange
命令会获取列表指定索引范围所有的元素。索引下标有两个特点:
- 索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。
lrange
中索引范围是前闭后闭,这个和很多编程语言前闭后开不太相同。
lrange 键 开始下标 结束下标
获取列表的第2到第4个元素:
真切片:按照索引范围修剪列表,原列表变为切片后的列表。
ltrim 键 开始下标 结束下标
左弹出:从列表左侧弹出元素。
lpop 键
将列表最左侧的元素c会被弹出,弹出后列表变为java、b、a:
右弹出:从列表右侧弹出。
rpop 键
右弹出左推入:从列表A的最右边弹出元素,加入到列表B的最左边。
rpoplpush 键A 键B
指定删除:删除指定元素。
lrem 键 count 指定元素
lrem
命令会从列表中找到等于 value
的元素进行删除,根据count的不同分为三种情况:
- count>0,从左到右,删除最多count个元素。
- count<0,从右到左,删除最多count绝对值个元素。
- count=0,删除所有。
key
:列表的键名。count
:要移除的元素数量。- 如果
count
大于 0,那么命令会从列表头部开始搜索,移除最多count
个匹配的元素。 - 如果
count
小于 0,那么命令会从列表尾部开始搜索,移除最多count
个匹配的元素。注意这里的“最多”是因为列表中可能没有足够多的匹配元素。 - 如果
count
等于 0,那么命令会移除列表中所有匹配的元素。
- 如果
value
:要移除的元素的值。
时间复杂度、使用场景
**列表比较典型的使用场景就是消息队列。**使用 rpush
和 lpush
操作入队列,lpop
和 rpop
操作出队列。实际上列表的使用场景很多,在选择时可以参考以下口诀:lpush+lpop=Stack(栈)
、lpush+rpop=Queue(队列)
、lpush+ltrim=Capped Collection(有限集合)
、lpush+brpop=Message Queue(消息队列)
。
集合
集合(set
)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过下标获取元素。
一个集合最多可以存储2的32次方减1个元素。
添加、遍历、删除
添加元素:向集合里面添加元素,返回结果为添加成功的元素个数。
sadd 键 元素1 元素2 元素3 ...
先添加元素a、b、c,返回值为 3,说明成功添加3个元素:
再向集合添加2个元素a、b,返回0说明没有元素成功添加,这是因为集合里已经有这两个元素了:
遍历:遍历所有元素。
smembers 键
获取集合myset所有元素,返回结果是无序的:
?>提示:smembers
和 lrange
、hgetall
都属于比较重的命令,如果元素过多存在阻塞Redis的可能性,这时候可以使用 sscan
来完成。
删除元素:删除集合中的元素,返回结果为成功删除元素个数。
srem 键 元素 [元素 ...]
例如,删除集合中的a、b元素返回2,删除d元素返回0:
长度、存在、随机
计算:计算元素个数。
scard key
**scard的时间复杂度为O(1),它不会遍历集合所有元素,而是直接用 Redis内部的变量,**例如:
存在:判断指定元素是否在集合中,存在返回1,反之返回0。
sismember 键 元素
例如,判断元素c、d是否在集合当中:
随机返回:随机从集合返回指定个数元素,如果指定个数超出集合长度,则全部返回。
srandmember 键 [count]
[count]
是可选参数,如果不写默认为1,例如:
随机弹出:从集合随机弹出元素,没有元素弹出时,返回null。
spop 键 [count]
[count]
是可选参数,如果不写默认为1,例如:
!> 提示:srandmember
和 spop
都是随机从集合选出元素,两者不同的是 spop
命令执行后,元素会从集合中删除,而 srandmember
不会。
交集、并集、差集
现在有两个集合,它们分别是 user1
和 user2
:
127.0.0.1:6379> sadd user1 it music his sports
(integer) 4
127.0.0.1:6379> sadd user2 it news ent sports
(integer) 4
交集:多个集合中共有的元素。
sinter 键 [键 ...]
求 user1
和 user2
两个集合的交集:
并集:多个集合中所有的元素。
suinon 键 [键 ...]
求 user1
和 user2
两个集合的并集:
差集:多个集合中除开共有的元素。
sdiff 键 [键 ...]
求 user1
减去 user2
的差集:
前面三个命令如图所示:
时间复杂度、使用场景
**集合类型比较典型的使用场景是标签(tag)。**例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。
有序集合
有序集合(zset
):它保留了集合不能有重复成员的特性, 但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。
!> 提示:有序集合中的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同。
添加、长度、分数
添加:集合内添加成员,返回结果代表成功添加成员的个数。
zadd 键 分数 成员 [分数 成员 ...]
向有序集合 user:ranking
添加用户tom和他的分数251:
长度:返回有序集合内的成员个数。
zcard 键
返回有序集合 user:ranking
的成员数:
获取分数:获取某个成员的分数,如果成员不存在则返回 null
。
zscore 键 成员
增加分数:给成员增加指定的分数,返回给成员增加后的分数。
zincrby 键 增加分数 成员
给tom增加了9分,分数变为了260分:
范围、排名、删除
分数范围个数:返回指定分数范围成员个数。
zcount 键 最小分数 最大分数
返回从200分到221分的成员的个数:
分数范围成员:返回指定分数范围的成员。
zrangebyscore 键 最小分数 最大分数 [withscores] [limit offset count]
zrevrangebyscore 键 最小分数 最大分数 [withscores] [limit offset count]
zrangebyscore
按照分数从低到高返回,zrevrangebyscore
反之。withscores
选项,同时会返回成员的分数[limit offset count]
选项可以限制输出的起始位置和个数。
成员排名:返回成员的排名(排名从0开始计算)。
zrank 键 成员
zrevrank 键 成员
zrank
是从分数从低到高返回排名,zrevrank
反之。
tom按分数由低到高排第5,按分数由高到低排第0:
获取成员:返回指定排名范围的成员。
# 分值排名由低到高返回
zrange 键 开始排名 结束排名 [withscores]
# 分值排名由高到低返回
zrevrange 键 开始排名 结束排名 [withscores]
- 其中
zrange
按照分数从低到高返回,zrevrange
反之。 - 加上
withscores
选项,同时会返回成员的分数
返回排名最低、最高的三个成员和分数:
删除:删除成员,返回结果为成功删除的个数。
zrem 键 成员 [成员 ...]
将成员mike从有序集合 user:ranking
中删除:
删除排名范围:删除指定排名内的升序元素。
zremrangebyrank 键 开始排名 结束排名
删除排名第1到第3名的成员:
删除分数范围:删除指定分数范围的成员,返回结果为成功删除的个数。
zremrangebyscore 键 最小分数 最大分数
将250分以上的成员全部删除:
交集、并集
zadd user:ranking:1 1 kris 91 mike 200 frank 220 tim 250 martin 251 tom
(integer) 6
zadd user:ranking:2 8 james 77 mike 625 martin 888 tom
(integer) 4
交集:多个集合中共有的元素。
zinterstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
- destination:交集计算结果保存到这个键。
- numkeys:需要做交集计算键的个数。
- key[key...]:需要做交集计算的键。
- weights weight[weight...]:每个键的权重,在做交集计算时,每个键中的每个member会将自己分数乘以这个权重,每个键的权重默认是1。
- aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、min(最小值)、max(最大值)做汇总,默认值是sum。
对 user:ranking:1
和 user:ranking:2
做交集,weights和aggregate使用了默认配置,可以看到目标键 user:ranking:1_inter_2
对分值做了sum操作:
并集:多个集合中所有的元素。
zunionstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
该命令的所有参数和zinterstore是一致的,只不过是做并集计算。
计算 user:ranking:1
和 user:ranking:2
的并集,weights和aggregate使用了默认配置,可以看到目标键 user:ranking:1_union_2
对分值做了sum操作:
时间复杂度、使用场景
**有序集合比较典型的使用场景就是排行榜系统。**例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数。本节使用赞数这个维度,记录每天用户上传视频的排行榜。
哈希
在Redis中,哈希类型是指键值本身又是一个键值对结构。
!> 注意:哈希类型中的映射关系叫作 field-value
,注意这里的 value
是指 field
对应的值,不是键对应的值。
添加、获取、删除
添加:添加一对域值。
hset 键 域 值
为 user
添加一对 {name:tom}
域值,如果设置成功会返回1,反之会返回0。
获取:获取域的属性值。
hget 键 域
获取 user
的 name
域(属性)对应的值,如果键或 field
不存在,会返回 nil
:
删除:删除哈希键中一个或多个 field
,返回结果为成功删除 field
的个数。
hdel 键 域 [域 ...]
删除 user
的 name
域,如果域不存在返回0:
批量添加、批量获取
hmset
批量设置 field-value
,需要的参数是 key
和多对 field-value
域值。
hmset 键 域 值 [域 值 ...]
hmget
是批量获取 field-value
,需要的参数是 key
和多个 field
,如果没有域就返回为null:
hmget 键 域 [域 ...]
获取全域、获取全值
获取所有 field
:
hkeys 键
获取所有的 value
:
hvals 键
获取所有的 field-value
:
hgetall 键
获取 user
全部 field
和 value
和 field-value
:
?> 提示:如果哈希元素个数比较多,在使用 hgetall
时,会存在阻塞Redis的可能。如果一定要获取全部 field-value
,可以使用 hscan
命令,该命令会渐进式遍历哈希类型。
存在、统计、计算
存在:判断 field
是否存在,存在返回结果为1,不存在时返回0。
hexists 键 域
统计:统计 field
个数。
hlen 键
计算:计算 field
对应的 value
的字符串长度。
hstrlen 键 域
例如,user
有3个 field
域:
时间复杂度、使用场景
**哈希类型比较典型的使用场景是信息存储。**相比其他的存储,用户信息存储更直观,但Redis去模拟关系型复杂查询开发困难,维护成本高,而关系型数据库可以做复杂的关系查询。
**哈希类型是稀疏的,而关系型数据库是完全结构化的。**例如,哈希类型每个键可以有不同的 field
,而关系型数据库一旦添加新的列,所有行都要为其设置值(即使为NULL)。
内部编码
Redis对外有5种数据结构分别是: string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合),实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码。
查询内部编码
可以看到每种数据结构都有两种以上的内部编码实现,可以通过 object encoding
命令查询内部编码:
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> object encoding hello
"embstr"
127.0.0.1:6379> rpush mylist a b c d e f g
(integer) 7
127.0.0.1:6379> object encoding mylist
"ziplist"
Redis这样设计有两个好处:第一,可以改进内部编码,而对外的数据结构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令。第二,多种内部编码实现可以在不同场景下发挥各自的优势。
字符串编码
字符串类型的内部编码有3种:
int:8个字节的长整型。
embstr:小于等于39个字节的字符串。
raw:大于39个字节的字符串。
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
整数类型示例如下:
127.0.0.1:6379> set key 8653
OK
127.0.0.1:6379> object encoding key
"int"
短字符串示例如下:
#小于等于39个字节的字符串:embstr
127.0.0.1:6379> set key "hello,world"
OK
127.0.0.1:6379> object encoding key
"embstr"
长字符串示例如下:
#大于39个字节的字符串:raw
127.0.0.1:6379> set key "one string greater than 39 byte........."
OK
127.0.0.1:6379> object encoding key
"raw"
127.0.0.1:6379> strlen key
(integer) 40
列表编码
列表类型的内部编码有两种:
ziplist
(压缩列表):当列表的元素个数小于list-max-ziplist-entries
配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value
配置时(默认64字节),Redis会选用ziplist
来作为列表的内部实现来减少内存的使 用。linkedlist
(链表):当列表类型无法满足ziplist
的条件时,Redis会使用linkedlist
作为列表的内部实现。
当元素个数较少且没有大元素时,内部编码为 ziplist
:
127.0.0.1:6379> rpush listkey e1 e2 e3
(integer) 3
127.0.0.1:6379> object encoding listkey
"ziplist"
当元素个数超过512个或者当某个元素超过64字节,内部编码变为 linkedlist
:
127.0.0.1:6379> rpush listkey e4 e5 ... e512 e513
(integer) 513
127.0.0.1:6379> object encoding listkey
"linkedlist"
127.0.0.1:6379> rpush listkey "one string is bigger than 64 byte..."
(integer) 4
127.0.0.1:6379> object encoding listkey
"linkedlist"
集合编码
集合类型的内部编码有两种:
intset
(整数集合):当集合中的元素都是整数且元素个数小于set-max- intset-entries
配置(默认512个)时,Redis会选用intset
来作为集合的内部实现,从而减少内存的使用。hashtable
(哈希表):当集合类型无法满足intset
的条件时,Redis会使用hashtable
作为集合的内部实现。
当元素个数较少且都为整数时,内部编码为 intset
:
127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 4
127.0.0.1:6379> object encoding setkey
"intset"
当元素个数超过512个或者当某个元素不为整数时,内部编码变为 hashtable
:
127.0.0.1:6379> sadd setkey 1 2 3 4 5 6 ... 512 513
(integer) 509
127.0.0.1:6379> scard setkey
(integer) 513
127.0.0.1:6379> object encoding listkey
"hashtable"
127.0.0.1:6379> sadd setkey a
(integer) 1
127.0.0.1:6379> object encoding setkey
"hashtable"
有序集合编码
有序集合类型的内部编码有两种:
ziplist
(压缩列表):当有序集合的元素个数小于zset-max-ziplist- entries
配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value
配 置(默认64字节)时,Redis会用ziplist
来作为有序集合的内部实现,ziplist
可以有效减少内存的使用。skiplist
(跳跃表):当ziplist
条件不满足时,有序集合会使用skiplist
作为内部实现,因为此时ziplist
的读写效率会下降。
当元素个数较少且每个元素较小时,内部编码为 skiplist
:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3
(integer) 3
127.0.0.1:6379> object encoding zsetkey
"ziplist"
当元素个数超过128个或者当某个元素大于64字节时,内部编码变为 ziplist
:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3 12 e4 ... 84 e129
(integer) 129
127.0.0.1:6379> object encoding zsetkey
"skiplist"
127.0.0.1:6379> zadd zsetkey 20 "one string is bigger than 64 byte..."
(integer) 1
127.0.0.1:6379> object encoding zsetkey
"skiplist"
哈希编码
哈希类型的内部编码有两种:
ziplist
(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries
配置(默认512个)、同时所有值都小于hash-max-ziplist-value
配置(默认64 字节)时,Redis会使用ziplist
作为哈希的内部实现,ziplist
使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable
更加优秀。hashtable
(哈希表):当哈希类型无法满足ziplist
的条件时,Redis会使用hashtable
作为哈希的内部实现,因为此时ziplist
的读写效率会下降,而hashtable
的读写时间复杂度为O(1)
。
当 field
个数比较少且没有大的 value
时,内部编码为 ziplist
:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2
OK
127.0.0.1:6379> object encoding hashkey
"ziplist"
当有 value
大于64字节或者当 field
个数超过512,内部编码会由 ziplist
变为 hashtable
:
127.0.0.1:6379> hset hashkey f3 "one string is bigger than 64 byte..."
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2 f3 v3 ... f513 v513
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"
HINCRBY命令是Redis中用于对hash类型数据中某个字段的值进行增加操作的命令。它可以将指定字段的值作为整数处理,并将增加的结果保存在该字段中。
HINCRBY命令的语法格式如下:
HINCRBY key field increment
其中,key表示哈希数据结构的键名,field表示哈希数据结构中的字段名,increment表示要增加的值。增量increment可以是正数或负数,如果是负数,那么相当于对指定字段进行减法操作。
如果key不存在,那么HINCRBY命令会创建一个新的哈希表并执行增加操作。如果field不存在,那么在执行命令前,field的值会被初始化为0。但请注意,对一个存储字符串值的field执行HINCRBY命令会造成错误。
执行HINCRBY命令后,会返回哈希表key中field的新值。此命令的时间复杂度为O(1),操作的值被限制在64位有符号数字表示之内。
总的来说,HINCRBY命令是Redis中用于处理哈希表中字段值增减的非常有用的命令。