目录
Redis包含5中基本的数据类型。Redis中存储的数据是以键值对的形式进行存储的,Redis的数据类型指的是value部分的数据类型,而key部分则一直都是一个string字符串。
1、string字符串
字符串常用来缓存用户信息,例如将用户信息的JSON序列化成字符串,存入到Redis。
Redis的字符串是动态的、可修改的,这点和Java的字符串是不同的,Redis的字符串类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
当字符串长度小于1MB的时候,扩容是加倍现有的空间;如果字符串长度超过1MB,一次只会多扩1MB的空间。字符串的最大长度是512MB。
1.1 键值对操作
对单个键值对的增删改查:
// 设置name = zhangsan(增或改)
set name zhangsan
// 如果属性不存在就执行set操作,可以理解为 set if not exists (增)
setnx name lisi
// 查询属性name对应的value(查)
get name
// 查询属性name是否存在(查)
exists name
// 删除属性name及其对应的value(删)
del name
1.2 批量键值对操作
对多个键值对进行读写,以节省网络耗时开销。
// 批量设置
mset name1 zhangsan name2 lisi name3 wangwu
// 批量获取
mget name1 name2 name3
1.3 过期
可以对key设置过期时间,到时间后会自动删除,以控制缓存的失效时间。
// 设置name 5s后超时,单位是s
expire name 5
// 设置name = lisi,且超时时间为5s
setex name 5 lisi
1.4 计数
如果value是一个整数,还可以对它进行自增操作。自增的范围是有符号long型的最大值和最小值。
set age 30
// 递增,每次+1
incr age
// 递增,每次加2, 2可以设置
incrby age 2
// 递减,每次减1
decr age
// 递减,每次减2, 2可以设置
decrby age 2
2、list列表
Redis的列表类似于Java中的LinkedList,它的数据结构是基于双向链表,而不是基于数组。
Redis的列表经常用来做异步队列使用,将需要延后处理的任务结构体序列化并放入Redis的列表,另一个线程从这个列表中轮询数据进行处理。
由于Redis的列表是双向链表,使用不同的使用方式可以分别达到队列和栈的效果。
其实Redis的列表并不是简单的双向链表,而是一种“快速链表(quicklist)”。在列表元素较少的情况下,会使用一块连续的内存存储数据,这种结构叫做压缩列表(ziplist);当数据量比较大的时候,则会修改为quicklist,而quicklist实际上是链表和ziplist结合使用的一个结果。由于ziplist需要的是连续的内存空间,数据量较大的时候分配多个连续的大块内存,会造成空间冗余,导致内存空间的浪费,同时还会加重内存的碎片化。
2.1 队列:反方向提交和获取
队列的特点是先进先出。可以通过在一个方向放入数据,在另一个方向弹出数据,来实现队列的结构。
// 从右侧向列表中按照前后顺序依次放入多个元素。r表示right
rpush names zhangsan lisi wangwu
// 获取列表的长度
llen names
// 从左侧出栈一个数据,数据弹出后列表中就没有这个元素了。l表示left
lpop names
当然,上述方向反过来,从左边放入从右边弹出元素,也是可以的。
2.2 栈:同方向提交和获取
栈是后入先出的数据结构,可以通过在同一个方向放入元素和弹出数据来实现。使用到的命令和队列基本相同。
// 从右侧向列表中按照前后顺序依次放入多个元素。r表示right
rpush names zhangsan lisi wangwu
// 从又侧出栈一个数据,数据弹出后列表中就没有这个元素了。l表示left
rpop names
2.3 列表操作
上面两节用到的都是对端点元素的放入和弹出操作,根据链表的特性可知,时间复杂度都是O(1)。作为列表,还有一些基于列表特性的一些操作,不过这些操作的时间复杂度大多会高一些。
有如下相关命令:
rpush names zhangsan lisi wangwu
// 查询
// 获取指定index的元素,时间复杂度是O(n)
lindex names 1
// 获取指定范围的全部元素,时间复杂度是O(n)
lrange names 0 -1
// 获取列表的长度
llen names
// 删除
// 清除指定范围的全部元素,时间复杂度是O(n)
ltrim names 1 -1
// 删除从左向右数的前1个值为李四的节点
lrem names 1 lisi
// 增加和修改
// 设置指定索引位置的元素的值,索引为0表示左边第一个元素
lset names 1 zhaoliu
// 在lisi后面插入元素zhaoliu(后面指的是右边)
linsert names after lisi zhaoliu
// 其他
// 把一个列表的元素转移到另一个列表
rpoplpush names newnames
lindex:
lindex用来获得指定索引的元素值。索引从0开始,左边第一个元素的索引是0。
语法:LINDEX key index
lrange:
lrange命令是列表类型最常用的命令之一,获取列表中的某一片段,将返回start、stop之间的所有元素(包含两端的元素),索引从0开始,左边的第一个元素的索引是0。索引可以是负数,如:“-1”代表最后边的一个元素。
语法:LRANGE key start stop
llen:
llen用来获取列表中元素的个数。
语法:LLEN key
ltrim:
ltrim清除指定范围的全部元素。指定范围和lrange一致。
语法:LTRIM key start stop
lrem:
lrem命令会删除列表中从左数前count个值为value的元素,返回实际删除的元素个数。根据count值的不同,该命令的执行方式会有所不同:
- 当count>0时, LREM会从列表左边开始删除。
- 当count<0时, LREM会从列表后边开始删除。
- 当count=0时, LREM删除所有值为value的元素。
语法:LREM key count value
lset
设置指定索引的元素值。索引是从0开始的,左边第一个元素的索引值为0。
语法:LSET key index value
linsert:
该命令首先会在列表中从左到右查找值为pivot的元素,然后根据第二个参数是BEFORE还是AFTER来决定将value插入到该元素的前面还是后面。
语法:LINSERT key BEFORE|AFTER pivot value
rpoplpush:
将元素从一个列表转移到另一个列表中。
语法:RPOPLPUSH source destination
3、字典hash
hash类型也叫字典或者散列类型,它提供了字段和字段值的映射,它的字段和字段值都只能是字符串类型。hash类型也可以用来存储用户信息。和string相比,hash不需要全部序列化用户数据,可以单独保存用户数据中的每个字段,以方便读取,节省网络流量。当然hash结构也有不足,它的存储消耗是要高于string的。
Redis的hash类型类似于Java中的HashMap,是一种无序的字典结构。hash类型的底层也是基于数组+链表来实现的,当发生hash冲突的时候,就把冲突的元素放入到同一个链表中。
3.1 渐进式rehash
Redis的hash类型和Java的HashMap的rehash的方式是不同的。Java的HashMap的rehash是一次性rehash全部的元素,这样在字典很大的时候会导致耗时严重的问题。Redis为了追求高性能,采用渐进式rehash策略。
渐进式rehash的特点是,在rehash期间保留新旧两个hash结构,这样在查询的时候会同时查询两个hash结构,然后在后续的定时任务已经hash操作指令中,循序渐进地将旧hash的内容迁移到新的hash结构,直到迁移完毕,删除旧的hash结构。
3.2 设置操作
// 如果是插入字段值,则返回1;如果是更新字段值,则返回0
hset names zhang zhangsan
// 如果字段值中间有空格,则必须用双引号括起来
hset names li "li si"
// 批量设置字段值
hmset names wang wangwu zhao zhaoliu
// 当字段不存在时赋值,否则不执行赋值操作
hsetnx names li lidasi
3.3 查询操作
// 获取指定字段的字段值
hget names zhang
// 获取key对应的全部字段-字段值组合
hgetall names
// 一次获取多个字段的字段值
hmget names zhang li wang
// 获取hash的字段数量
hlen names
// 字段是否存在。返回值为1表示字段存在
hexists names zhang
3.4 获取集合
// 获取全部字段名
hkeys names
// 获取全部字段值
hvals names
3.5 删除操作
// 删除指定字段,可以是单个或多个字段。返回值是被删除的字段个数
hdel names zhang li
3.6 单个字段的操作
hash还可以对单个字段进行操作,包括计数、增加等。
hset ages zhangsan 20
// 按1的步长递增
hincrby ages zhangsan 1
4、集合set
Redis的set相当于Java的HashSet,内部是无序且不可重复的字段。它的内部实现也和Java的HashSet类似,是一个特殊的字典,只不过所有的字段值都是null。
如果set的全部元素都被移除后,集合的数据结构将会被删除,内存空间也会被回收。
4.1 基本操作
// 向集合中加入一个元素
sadd names zhangsan
// 查询集合中的全部元素
smenbers names
// 查询集合中是否存在指定元素。返回1表示存在,0表示不存在
sismember names zhangsan
// 计数集合中的元素数量
scard names
// 从集合中弹出一个元素。一般用在遍历并清空结合的操作
spop names
// 删除指定的一个或多个元素
srem names zhangsan lisi
4.2 集合运算
sadd setA 1 2 3
sadd setB 2 3 4
// 差集:返回属于A并且不属于B的元素集
sdiff setA setB
// 交集:属于A且属于B的元素元素集
sinter setA setB
// 并集:属于A或者属于B的元素集
sunion setA setB
5、有序集合zset
Sortedset又叫zset。Sortedset是有序集合,可排序并且不可重复。zset兼具Java的SortedSet和HashMap的特性。一方面zset是一个集合,具备集合的特性,即内部元素是唯一的;另一方面,它也给每个元素赋予一个分数,代表这个元素的排序权重,然后通过这个分数进行排序。
跟set一样,当最后一个元素都被移除后,会删除结构并回收内存。
5.1 增加元素
// 增加元素
zadd names 5 zhangsan
zadd names 4.5 lisi
zadd names 3.7 "wang wu"
5.2 获取元素
// 获取元素的分数
zscore names zhangsan
// 获取元素从小到大的排名
zrank names zhangsan
// 获取元素从大到小的排名
zrevrank names zhangsan
5.3 获取范围
// 打印范围内的全部元素,按照元素分数从小到大的顺序返回,包含两端的元素
// 下标参数start和stop都以0为底,也就是说,以0表示有序集第一个成员,以1表示有序集第二个成员,以此类推
// 负数下标,以-1表示最后一个成员,-2 表示倒数第二个成员,以此类推。
zrange names 0 -1
// 逆序列出全部元素
zrevrange names 0 -1
// 获得指定分数范围的元素
zrangebyscore names 0 4.6
// inf即infinite,表示无穷大;withscores表示返回结果携带着分数值
zrangebyscore names -inf 4.6 withscores
5.4 计数个数
// 计数集合中元素的个数
zcard names
// 获取分数范围内的元素个数
zcount names 4 5
5.5 删除元素
// 删除元素
zrem names lisi
// 删除分数范围内的元素
zremrangebyscore names 0 4
// 删除排名范围内的元素
zremrangebyrank names 0 1
5.6 增加元素分数
// zhangsan的分数增加4分
zincrby names 4 zhangsan
5.7 跳表
由于当zset插入元素的时候,要按排序结果插入到合适的位置,所以zset需要支持随机的插入和删除,所以不适合使用数组来实现;而如果使用链表来实现,又不能很快的定位指定的位置。实际上,zset内部的排序功能是通过跳表来实现的。跳表查询和插入、删除的时间复杂度都是O(lgn)。
6、通用命令
还有一些是全部或者多个数据类型通用的操作。
6.1 过期时间
Redis所有的数据结构都可以设置过期时间,时间到了Redis会自动删除相应的对象。需要注意的是,过期是以Redis的一组key-value为单位的,例如hash类型,已过期整个hash对象和它对应的key都会过期。
另外,如果一个字符串已经设置了过期时间,然后调用set方法修改了它,那么过期时间失效。
// 设置name 5s后超时,单位是s
expire name 5
// 查看key生于的生存时间
ttl key
// 清除生存时间
persist key
// 生存时间设置单位为:毫秒
pexpire key milliseconds
6.2 容器型数据结构的通用规则
list、hash、set、zset四种数据结构是容器型的数据结构,共享以下两条规则:
- create if not exists:如果容器不存在,就创建一个,再进行操作;
- drop if no elements:如果容器中不包含元素了,就立刻删除容器,释放内存。
6.3 其他命令
// 列出符合给定pattern的所有key
keys name*
// 确认一个key 是否存在
exists name
// 删除一个key
del name
// 重命名key
rename name name_new
// 返回key对应的值的类型
type name