commands:redis.io/commands
前言
Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。不同类型的数据结构的差异就在于 value 的结构不一样。
Redis 有 5 种基础数据结构,分别为:
string(字符串)list(列表)set(集合)hash(哈希)zset(有序集合)
string 字符串
string 是 Redis 最基本的数据结构。字符串结构使用非常广泛,一个常见的用途就是缓存用户信息。程序中将用户信息结构体使用 JSON 序列化成字符串,然后将序列化后的字符串塞进 Redis 来缓存。同样,取用户信息会经过一次反序列化的过程。
string 类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。
Redis 的字符串是 动态字符串,是可以修改的字符串,内部结构实现上类似于 Java 的
ArrayList,采用 预分配冗余空间 的方式来减少内存的频繁分配。内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len。当字符串长度小于 1M 时, 扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是 字符串最大长度为 512M。
命令
redis> SET mykey "Hello"
"OK"
redis> GET mykey
"Hello"
redis> SET anotherkey "will expire in a minute" EX 60 #60s过期
"OK"
redis>
list 列表
Redis 的 list 相当于 Java 语言里面的 LinkedList,注意它是 链表 而不是数组。这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)。Redis 的 list 类型其实就是一个 每个子元素都是 string 类型的双向链表。 当 list 弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。
Redis 的 list 的主要功能是 push、pop、获取一个 范围的所有值 等等,这使得 list 既可以用作 栈,也可以用作 队列。操作中 key 理解为链表的名字,可以添加一个元素到列表的头部(左边)或者尾部(右边)。
Redis 的
list结构常用来做 异步队列 使用。将需要延后处理的任务结构体序列化成字符 串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。
list的pop操作还有阻塞版本的,当[lr]pop一个list对象时,如果list是空, 或者不存在,会立即返回nil。但是阻塞版本的b[lr]pop则可以阻塞,当然可以加超时时间,超时后也会返回nil。为什么要阻塞版本的 pop 呢?主要是为了避免轮询。举个简单的例子:如果用
list来实现一个工作队列,执行任务的thread可以调用阻塞版本的pop去获取任务,这样就可以避免轮询去检查是否有任务存在。当list中有任务的时候工作线程可以立即返回, 也可以避免轮询带来的延迟。
慢查询操作
慎用。lindex 相当于 Java 链表的 get(int index) 方法,它需要对链表进行遍历,时间复杂度为 O(n),性能随着参数 index 增大而变差。
命令
右进左出:队列
redis> RPUSH mylist "hello" "world"
(integer)2
redis> LLEN mylist
(integer)2
redis> LRANGE mylist 0 -1
1) "hello"
2) "world"
redis> LPOP mylist
"hello"
redis> LPOP mylist
"world"
redis> LPOP mylist
(nil)
右进右出:栈
redis> RPUSH mylist "hello" "world"
(integer) 2
redis> RPOP mylist
"world"
redis> RPOP mylist
"hello"
redis> LPOP mylist
(nil)
hash 字典
Redis 的 hash 是一个 string 类型的 field(域) 和 value(值) 的映射表,相当于 Java 语言里面的 HashMap,它是 无序字典。所以 Redis 的 hash 是指 键值对 中的值本身又是一个键值对结构,形如value=[{field1,value1},...{fieldN,valueN}]。
它的添加、删除操作的时间复杂度都是 O(1)(平均)。hash 特别适合用于存储对象。相较于将对象的每个字段存成单个 string 类型,将一个 对象 存储在 hash 类型中会占用更少的内存,并且可以更方便的存取整个对象。当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。
Redis 中每个 hash 可以存储
2^32 - 1键值对(40多亿)
hash 结构也可以用来存储用户信息,不同于字符串一次性需要全部序列化整个对象, hash 可以对用户结构中的每个字段单独存储。这样当需要获取用户信息时可以进行部分获取。而以整个字符串的形式去保存用户信息的话就只能一次性全部读取,这样就会比较浪费网络流量。 不过需要注意一点,
hash结构的存储消耗要高于单个字符串。
命令
redis> HSET myhash field1 "Hello"
(integer) 1
redis> HSET myhash field2 "World"
(integer) 1
redis> HGETALL myhash # key 和 value 间隔出现
1) "field1"
2) "hello"
3) "field2"
4) "world"
redis> HLEN myhash
(integer) 2
redis> HGET myhash field1
"hello"
redis> HSET myhash field1 "redis" # 更新操作,返回 0
(integer) 0
set 集合
Redis 的 set 相当于 Java 语言里面的 HashSet,它内部的 键值对是无序的唯一的。当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。操作中 key 理解为集合的名字,set 的操作有添加删除元素,有对多个 set 求交并差等操作。 set 是通过 hashtable 实现的,所以添加、删除和查找的复杂度都是 O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)
set结构可以用来存储活动中奖的用户 ID,因为有去重功能,可以保证同一个用户不会中奖两次。
命令
redis> sadd myset "hello"
(integer) 1
redis> sadd myset "hello" # 重复
(integer) 0
redis> sadd myset "world" "and" "redis"
(integer) 3
redis> smembers myset # 注意顺序,和插入的并不一致,因为 set 是无序的
1) "redis"
2) "and"
3) "hello"
4) "world"
redis> sismember myset "hello" # 查询某个 value 是否存在,相当于 contains(o)
(integer) 1
redis> scard myset # 获取长度相当于 size()
(integer) 4
redis> spop myset # 弹出一个
"world"
zset 有序列表
zset 类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的 唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的 排序权重。zset 中最后一个 value 被移除后,数据结构自动删除,内存被回收。
Redis zset 和 set 一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数( score )却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是
O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
zset可以用来存粉丝列表,value值是粉丝的用户ID,score是关注时间,对粉丝列表按关注时间进行排序。zset还可以用来存储学生的成绩,value值是学生的ID,score是他的考试成绩。对成绩按分数进行排序就可以得到他的名次。
命令
redis> zadd myzset 9.0 "java"
(integer) 1
redis> zadd myzset 8.5 "C++"
(integer) 1
redis> zadd myzset 8.8 "C"
(integer) 1
redis> zrange myzset 0 -1 #按 score 排序列出,参数区间为排名范围
1) "C++"
2) "C"
3) "java"
redis> zrevrange myzset 0 -1 # 按 score 逆序列出,参数区间为排名范围
1) "java"
2) "C"
3) "C++"
redis> zcard myzset # 相当于 size()
(integer) 3
redis> zscore myzset "C++" # 获取指定 value 的 score
"8.5"
redis> zrank myzset "java" # 排名
(integer) 2
redis> zrangebyscore myzset 0 8.9 # 根据分值区间遍历 zset
1) "C++"
2) "C"
redis> zrangebyscore myzset -inf 8.9 withscores # 根据分值区间 (-∞, 8.91] 遍历 zset,同时返 回分值。inf 代表 infinite,无穷大的意思。
1) "C++"
2) "8.5"
3) "C"
4) "8.8000000000000007" # 内部 score 使用 double 类型进行存储,所以存在小数点精度问题
redis> zrem myzset "C++" # 删除 value
(integer) 1
redis> zrange myzset 0 -1
1) "C"
2) "java"
总结
list/set/hash/zset 这四种数据结构是容器型数据结构,它们共享下面两条通用规则:
- create if not exists
如果容器不存在,那就创建一个,再进行操作。比如 rpush 操作刚开始是没有列表的, Redis 就会自动创建一个,然后再 rpush 进去新元素。
- drop if no elements
如果容器里元素没有了,那么立即删除元素,释放内存。这意味着 lpop 操作到最后一个元素,列表就消失了。
过期时间
Redis 所有的数据结构都可以设置过期时间,时间到了,Redis 会自动删除相应的对象。 需要注意的是过期是以 对象 为单位。比如一个 hash 结构的过期是整个 hash 对象的过期, 而不是其中的某个子 key。 还有一个需要特别注意的地方是 如果一个字符串已经设置了过期时间,然后你调用了 set 方法修改了它,它的过期时间会消失。
redis> set mystring java
OK
redis> expire mystring 60
(integer) 1
redis> ttl mystring
(integer) 55
redis> set mystring redis
OK
redis> ttl mystring
(integer) -1