Redis 基础数据结构

Redis 有 5 种基础数据结构,分别为:string (字符串)、list (列表)、set (集合)、hash (哈 希) 和 zset (有序集合)。

string 字符串

字符串 string 是 Redis 最简单的数据结构。

Redis 的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,内部为当前字 符串实际分配的空间 capacity 一般要高于实际字符串长度 len。当字符串长度小于 1M 时, 扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是 字符串最大长度为 512M。

常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 键值操作
set key value
get key
exists key
del key

# 批量键值操作
mget key1 key2 key3
mset key1 value1 key2 value2 key3 value3

# 过期和 set 指令扩展
expire key 5 # 5秒后过期
setex key 5 value # 5秒后过期 等价于set+expire
setnx key value # 如果 key 不存在,则执行 set 创建并返回 1,key 存在则创建失败并返回0

# 计数
# 如果 value 的值是一个整数,还可以进行自增操作。自增的范围为 long 的最大和最小值
set age 15
incr age
incrby age 5 # 自增步长为 5,15+5=20
incrby age -5 # 相当于自减步长为5,20-5=15
# 自减
decr age
decrby age 5

list 列表

Redis 里的列表相当于 Java 的 LinkedList,注意它是链表不是数组,所以插入和删除的速度非常快 O(1), 但是索引定位很慢 O(n)

当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。

Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符 串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。

队列: 先进先出

1
2
3
4
5
6
7
# 从右边进,左边出
rpush books python java golang
lpop books

# 从左边进,右边出
lpush books python java golang
rpop books

栈: 先进后出

1
2
3
4
5
rpush books python java golang
rpop books

lpush books python java golang
lpop books

慢操作

lindex 相当于 Java 链表的 get(int index)方法,它需要对链表进行遍历,性能随着参数 index 增大而变差。 谨慎使用!除此之外还有 lrange, ltrim

1
2
3
4
5
6
7
8
9
10
# O(n) 慎用
lindex books 1

# O(n) 慎用
# 获取所有元素, -1是最后一个元素, [0,-1]
lrange books 0 -1

# O(n) 慎用
# 保留 第 2 个元素到最后一个元素,即之外的元素都砍掉, [1,-1]
ltrim books 1 -1

hash 字典

Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典。内部实现结构上同 Java 的 HashMap 也是一致的,同样的数组 + 链表二维结构。第一维 hash 的数组位置碰撞 时,就会将碰撞的元素使用链表串接起来。

不同的是,Redis 的字典的值只能是字符串,另外它们 rehash 的方式不一样,因为 Java 的 HashMap 在字典很大时,rehash 是个耗时的操作,需要一次性全部 rehash。Redis 为了高性能,不能堵塞服务,所以采用了渐进式 rehash 策略。

渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个 hash 结构,然后在后续的定时任务中以及 hash 的子指令中,循序渐进地将旧 hash 的内容 一点点迁移到新的 hash 结构中。

当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。

hash 也有缺点,hash 结构的存储消耗要高于单个字符串,到底该使用 hash 还是字符 串,需要根据实际情况再三权衡。

1
2
3
hset books java "thinking in java"
hget books java
hlen books

set 集合

Redis 的集合相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的 内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL。

当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。 set 结构可以用来 存储活动中奖的用户 ID,因为有去重功能,可以保证同一个用户不会中奖两次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sadd books java
sadd books go python

# 获取所有元素, 注意和插入的顺序不一定一致,因为 set 是无序的
smembers books

# 查询某个元素是否存在
sismember books java

# 查询长度, 相当于 count
scard books

# 弹出一个
spop books

zset 有序列表

zset 类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权 重。它的内部实现用的是一种叫着「跳跃列表」的数据结构。

zset 中最后一个 value 被移除后,数据结构自动删除,内存被回收。

zset 可以用来存 粉丝列表,value 值是粉丝的用户 ID,score 是关注时间。我们可以对粉丝列表按关注时间 进行排序。
zset 还可以用来存储学生的成绩,value 值是学生的 ID,score 是他的考试成绩。我们 可以对成绩按分数进行排序就可以得到他的名次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
zadd books 9.0 "think in java"
zadd books 8.9 "java concurrency"

# 按 score 排序列出,参数区间为排名范围
zrange books 0 -1
# 按 score 逆序列出,参数区间为排名范围
zrevrange books 0 -1

# 相当于 count()
zcard books

# 获取指定 value 的 score
# 内部 score 使用 double 类型进行存储,所以存在小数点精度问题
zscore books "java concurrency"

# 排名
zrank books "java concurrency"

# 根据分值区间遍历 zset
zrangebyscore books 0 8.91

# 根据分值区间 (-∞, 8.91] 遍历 zset,同时返回分值。inf 代表 infinite,无穷大的意思。
zrangebyscore books -inf 8.91 withscores

# 删除 value
zrem books "java concurrency"