Redis中的数据结构
一、Redis概述
如今,互联网项目越来越青睐 Redis。
Redis 是一种基于内存的数据库。因为是面向内存,所以其读写性能要远优秀于面向磁盘的数据库。这也是在高并发场景下,Redis 发挥其作用的主要原因。
二、5种数据类型
Redis 是一种键值(key-value)数据库。这是理解其数据结构的关键。因为任何存放在 Redis 数据库中的数据都是以这种形式存在的。
在 Redis 中,Key 是一个简单的 String。Key 的长度没有限制,但是减小 Key 的长度可以有效的节约内存,也更有利于 Key 检索的效率。
而在 Redis 中,Value 则有 5 种数据类型。除了基本类型 String 外,还有 List、Hash、Set、Zset 四种数据类型。
1、String
字符串是 Redis 中最基本的数据类型,也是 Redis 中默认的数据类型。
下面是 String 种常用的命令。
127.0.0.1> set key1 value1 //向Redis中新增一个键值对
OK
127.0.0.1> get key1 //获得该键指向的字符串的值
"value1"
127.0.0.1> strlen key1 //获得该键指向的字符串的长度
(integer) 6
127.0.0.1> getset key1 newvalue //将该键指向字符串设为新值,返回旧值
"value1"
127.0.0.1> get key1 //测试上面的命令是否成功
"newvalue"
127.0.0.1> getrange key1 3 7 //获取该键指向字符串的子串,范围为0~len
"value"
127.0.0.1> append key1 more //向该键指向字符串后添加新串,返回总长度
(integer) 12
127.0.0.1> get key1 //测试上面命令是否成功
"newvaluemore"
Redis 还提供对整数和浮点数的简单运算,命令如下。
127.0.0.1> set key1 10 //向Redis中新增键值对
OK
127.0.0.1> incr key1 //对该键所指向的值,做加1运算
(integer) 11
127.0.0.1> incrby key1 10 //对该键所指向的值,加上指定的整数
(integer) 21
127.0.0.1> decr key1 //对该键所指向的值,做减1运算
(integer) 20
127.0.0.1> decrby key1 10 //对该键所指向的值,减去指定的整数
(integer) 10
这里要注意的是如果 value 值为浮点数,以上四个命令都会失败。想要操作浮点数,可以使用下面的命令。
127.0.0.1> set key1 1.1
OK
127.0.0.1> incrbyfloat key1 1.1 //这个命令对浮点数和整数都适用
"2.2"
可以看到,Redis 中自带的运算功能是比较弱的。不过 Redis 中提供 Lua 语言的支持,这是 Redis 扩展功能中最强大的一个地方。
2、List
Redis中的 List 是一个双向链表结构,也是一个有序的线性结构。所以它的优缺点也是十分明显的,优点就是:
- 插入和删除很方便
而其缺点:
- 查找性能很弱
由于是双向链表,所以 List 结构的命令分为左操作和右操作两种,示例如下。
127.0.0.1> lpush left_key node_1 node_2 node_3 //链表逆序加入节点
(integer) 3
127.0.0.1> lrange left_key 0 2 //查看链表中下标从0到2的节点值
1) "node_3"
2) "node_2"
3) "node_1"
127.0.0.1> rpush right_key node_1 node_2 node_3 //链表顺序加入节点
(integer) 3
127.0.0.1> lrange right_key 0 2 //查看链表中下标从0到2的节点值
1) "node_1"
2) "node_2"
3) "node_3"
127.0.0.1> lindex left_key 2 //读取指定链表中下标为2的节点,以左操作
"node_1"
127.0.0.1> llen left_key //返回指定链表的长度,以左操作
(integer) 3
127.0.0.1> lpop left_key //删除链表最左边的节点并返回
"node_3"
127.0.0.1> rpop left_key //删除链表最右边的节点并返回
"node_1"
127.0.0.1> lrange left_key 0 0 //测试上面两条命令是否生效
1) "node_2"
127.0.0.1> lpushx left_key node_1 //如果存在该链表,则在最左边
(integer) 2 //添加一个节点,否则添加失败
127.0.0.1> rpushx left_key node_3 //如果存在该链表,则在最右边
(integer) 3 //添加一个节点,否则添加失败
127.0.0.1> lrange left_key 0 2 //测试上面两条命令是否生效
1) "node_1"
2) "node_2"
3) "node_3"
127.0.0.1> lset left_key 0 newNode_1 //修改执行下标的节点值
OK
127.0.0.1> lrange left_key 0 2 //测试上面一条命令是否生效
1) "newNode_1"
2) "node_2"
3) "node_3"
127.0.0.1> ltrim left_key 1 1 //修剪链表,保留指定下标区间的节点
OK
127.0.0.1> lrange left_key 0 0 //测试上面一条命令是否生效
1) "node_2"
需要注意的是,对链表的操作都是进程不安全的,有可能存在多个客户端同时操作链表的情况。为了保证链表操作的安全性,Redis 提供了阻塞命令,执行阻塞命令,会给链表加锁,可是这些命令在实际开发中使用的不多,这里不做过多介绍。
3、Set
set 翻译成中文就是集合。这里的集合同数学中的集合有着相同的性质:无序、不可重复。
同 list 一样,集合中每一个元素也都是 String 数据类型。
127.0.0.1:6379> sadd set1 val1 val2 val3 val4 val5 //给键为set1的
(integer) 5 //集合添加成员
127.0.0.1:6379> sadd set2 val4 val5 val6 val7 val8 //同上
(integer) 5
127.0.0.1:6379> smembers set1 //返回该键指向集合的所有成员
1) "val4" //从取出的成员能明显看出来集合是无序的
2) "val1"
3) "val3"
4) "val2"
5) "val5"
127.0.0.1:6379> sdiff set1 set2 //求两个集合的差
1) "val1"
2) "val3"
3) "val2"
127.0.0.1:6379> sinter set1 set2 //求两个集合的交
1) "val4"
2) "val5"
127.0.0.1:6379> sunion set1 set2 //求两个集合的并
1) "val4"
2) "val8"
3) "val7"
4) "val3"
5) "val5"
6) "val1"
7) "val6"
8) "val2"
127.0.0.1:6379> sismember set1 val1 //判断val1是否为指定集合的成员
(integer) 1
127.0.0.1:6379> smove set1 set2 val1 //把val1从set1移到set2中
(integer) 1
127.0.0.1:6379> smembers set2 //测试上一条命令是否生效
1) "val4"
2) "val5"
3) "val7"
4) "val8"
5) "val1"
6) "val6"
127.0.0.1:6379> spop set1 //随机弹出一个集合成员
"val2"
127.0.0.1:6379> spop set1 //同上
"val4"
127.0.0.1:6379> spop set1 //同上
"val3"
127.0.0.1:6379> smembers set1 //测试上面三条命令是否生效
1) "val5"
4、Zset
zset 称为有序集合。有序集合和集合相似,唯一不同的是有序集合中每一个元素还带有一个分数,可以根据这个分数来对集合成员进行排序。
下面介绍有序集合的一些常用命令。
127.0.0.1> zadd set 1 A 2 B 3 C 4 D 5 E //向有序集合中添加成员
(integer) 5
127.0.0.1> zcount set 2 4 //返回指定分数区间内的成员个数
(integer) 3
127.0.0.1> zincrby set 1 E //为指定成员增加指定分数值
"6"
127.0.0.1> zrange set 0 4 //返回指定下标区间内成员,分数从小到大排序
1) "A"
2) "B"
3) "C"
4) "D"
5) "E"
127.0.0.1> zrank set B //返回指定成员的排名,排名从0开始
(integer) 1
127.0.0.1> zadd set 2.5 X //添加一个分数为浮点数的成员
(integer) 1
127.0.0.1> zrange set 0 5 //测试上面一条命令是否生效
1) "A"
2) "B"
3) "X"
4) "C"
5) "D"
6) "E"
5、Hash
Redis 中的 Hash 就如同 Java 中的 map 一样。这种数据类型对于某些场景非常有用:比如需要在 Redis 中存储大量的 User 信息,每个 User 包括多个属性字段。这种情况使用 Hash 结构来存储就相当方便,实例如下。
127.0.0.1> hmset user_1 id 001 age 23 sex male //设置多个键值对
OK
127.0.0.1> hgetall user_1 //获取所有的键值对
1) "id"
2) "001"
3) "age"
4) "23"
5) "sex"
6) "male"
127.0.0.1> hexists user_1 id //是否存在id字段/键
(integer) 1
127.0.0.1> hincrby user_1 age 3 //age字段/键上加3
(integer) 26
127.0.0.1> hkeys user_1 //获得所有的键
1) "id"
2) "age"
3) "sex"
127.0.0.1> hvals user_1 //获得所有的值
1) "001"
2) "26"
3) "male"
127.0.0.1> hlen user_1 //返回该hash中键值对的数量
(integer) 3
127.0.0.1> hset user_1 name mary //向该hash中新增一个键值对
(integer) 1
127.0.0.1> hgetall user_1 //测试上面的命令是否生效
1) "id"
2) "001"
3) "age"
4) "26"
5) "sex"
6) "male"
7) "name"
8) "mary"
可以看出,此时键为 user_1,而值为一个 hash 结构,该 hash 结构中又存放了多个键值对。也就是说 Redis 需要通过 user_1 这个键来索引到对应的 hash 结构,再通过 hash 结构的键找到对应的值。