redis五大数据结构:string、list、hash、set、zset。
redis的数据结构是一个全局的Map结构,五大数据结构是值value的结构,用java表示,可以理解为Map<String,String>,Map<String,List>,Map<String,Set>,Map<String,String>,Map<String,Zset>。
1. string类型。
redis的字符串是动态字符串,即value类型为字符串时,其实是分配了一个数组来存放这个字符串的每个字符。如java的ArrayList。字符串最大长度是512MB,当小于1MB时,扩容时会加倍当前的容量,
当超过1MB后,每次扩容会加1MB。数据结构如下图所示。
字符串由多个字符组成,每个字符由8个bit位组成。所以字符串可以看成是多个bit位的组成。
1.1 操作命令
----------------------------创建数据操作------------------------
//单key值添加
set key value
//单key值查询
get key
//批量添加
mset key1 key2 key3...
//批量查询
mget key1 key2 key3...
//过期操作:10s后过期
expire key 10
//10s后过期,等价于 set+expire
setex key 10 value
//如果key不存在就执行set命令创建,如果存在就创建失败。分布式锁的实现。
setnx key value
----------------------------计数操作------------------------
//value值是整数的话可以进行计数操作,操作的数是有个范围的,超过Long.MAX会报错,大约是922万亿。
//也不能小于起始值。否则都会报错。
set jll 1
//对jll这个key进行+1操作
incr jll
//对jll这个key进行+2操作
incrby jll 2
//对jll这个key进行-2操作
incrby jll -2
2. list类型(列表)
Redis中的list类型,底层实现其实是链表结构,相当于java中的LinkedList。所以它的插入和删除复杂度为O(1),查询复杂度为O(N)。这个链表是个双向链表,所以能双向遍历读取。
结构如图2.1。
2.1 Redis中的list可以用来做队列使用:右边进左边出,右边进右边出。
右边进左边出:
//初始化一个队列
rpush student s1 s2 s3
//查询队列元素个数
llen student
//从左边取一个元素,取完后队列中就没有了s1这个元素。执行3次,队列中元素为空
lpop student
右边进右边出:
//初始化一个队列
rpush student s1 s2 s3
//从右边进右边出,下面命令会取出元素 s3。执行3次,元素为空。
rpop student
2.2 redis中 list 的慢操作,虽然是慢操作,但是根据使用场景,再一定的数据量下使用完全没有问题,数据量大的情况下慎用:
lindex:获取列表中某一位置的元素,如:lindex list 1 ---> 获取列表1索引位置为1的元素。
ltrim:定义列表某一区间的元素为一个列表,区间之外的元素删掉,如:一个list 有[1,2,3...20]20个元素,ltrim list 0 10 --->把这个list列表修改为[1,2,3...10]为10个元素的列表。然后执行 lrange list 0 -1 读取list所有元素为 [1,2,3...10],没修改执行读取的应为[1,2,3...20]。
lrange:遍历list列表元素。如: lrange list 0 10 --->读取list列表0到10号的元素。lrange list 0 -1 ---> 读取list列表所有元素。-1表示倒数第一个元素。-2表示倒数第二个元素。
2.3 redis中list慢操作的改进
2.2说的是redis的list是一个慢操作,但是redis底层实现其实是一个quicklist(快速列表)。它不是一个简单的linkedlist,它是一个ziplist+linkedlist。
ziplist:一个压缩列表,即 数据量少的时候是一个内存上空间连续的内存。
linkedlist:当数据量大的时候,它会是一个多个ziplist通过双向指针链接的list。
如下图所示:
3. hash字典
首先要说的是Redis中的hash字典和java中的HashMap的实现基本一样,都是数组+链表的数据结构。不同的是,它们的扩容机制不太一样,而且存储类型不一样,redis中hash的值只允许字符串。redis的存储和读取是高效的,基本不允许堵塞请求,而扩容是个耗时的操作,所以redis中的hash表的扩容是循序渐进的。
渐进式扩容:扩容的同时,会新建一个新的hash表,保留老的hash表,请求去读的时候会同时读取两个hash表,在后续的hash操作指令中,循序渐进的将上一版本的hash的内容迁移到新版本中,当搬迁完成时,会移除老版本中的最后一个元素,然后老hash会被内存回收,完成扩容。
从 String类型和hash类型的数据结构比较,我们不难发现,存储相同内容的数据,hash表存储更消耗内存,但是hash表中的子属性内容读取速度更快。如存储一个User对象,User对象中有age属性,那么存储User的时候,如果用string类型,那么底层是一个连续的数组,而用hash存储,是一个hashMap,所以hash更耗费内存,但是读取age的时候,hash更快。
hash的相关命令:
//创建hash表并存放数据
hset student xiaoming 1
hset student xiaohua 2
//从hash表中取值
hget student xiaoming
//获取所有student
hgetall student
//获取student的元素数量
hlen student
//批量存储元素
hmset student xiaoming 1 xiaohua 2
//批量获取student的元素
hmget student xiaoming xiaohua
//计数 +1
hincrby student xiaohua a
4. set 集合
Redis的集合相当于Java语言里面的HashSet,它内部的键值对是无序的、唯一的。java中的HashSet底层其实也是HashMap实现的,复杂度低查询快。它的内部实现相当于一个特殊的字典,字典中所有的value都是一个值NULL。当集合中最后一个元素被移除之后,数据结构被自动删除,内存被回收。所以Redis中的set可以使用在需要保持数据唯一性的场景。
set的相关命令:
//给user添加数据
sadd user xiaoming
//批量添加
sadd user ww ss gg
//查询user的数据
smembers user
//查询user中是否存在ww
sismember user ww
//查询某一个key中的数据的数量
scard user
//弹出一个元素
spop user
5. zset 集合
zset 比 set 集合多一个排序的功能。它类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是个set,保证了内部value的唯一,另一方面它可以给每个value赋予一个score,代表这个value排序权重。它的排序的底层实现用的是“跳表”的数据结构实现,对“跳表”数据结构感兴趣的可以参考其他文章。zset 和 set 一样,最后一个元素被删除之后,数据结构都会被删除,内存被回收。项目中某一项业务数据需要排序的时候,可以考虑使用zset。
//添加数据,score内部存的是double类型,取值的时候有可能存在精度问题
zadd xiaoming 50.0 "math"
zadd xiaoming 50.0 "yuwen"
//根据 key 取出所有元素并且按照 score 值升序排列
zrange xiaoming 0 -1
//统计key的所有元素
zcard xiaoming
//获取key的某一个元素的分数
zscore xiaoming "math"
//按照score的逆序列出,即按照score值降序排列
zrevrange xiaoming 0 -1
//统计某一key中存的元素数量
zcard xiaoming
//获取某一key的某一元素的排名
zrank xiaoming "math"
//获取某一key score区间的元素
zrangebyscore xiaoming 0 60.0
//获取某一key score区间的元素,并返回分值。-inf(infinite) 为负无穷大
zrangebyscore xiaoming -inf 60.0 withscores
//删除某一key中的指定元素
zrem xiaoming "math"
6. redis中过期时间的使用注意点
redis中的5大数据结构都可以设置过期时间,但是过期时间是相对于对象的,不是指容器中的某一具体元素。如hash表中存了很多元素,当设置hash表对应的key的过期时间后,当过期时,整个hash表中数据都会过期。
需要注意的另一个问题,如果一个字符串已经设置了过期时间,如果你调用了它的set方法,那么它的过期时间会消失。可以通过命令验证。
set user xiaoming
//过期时间设置
expire user 10000
//过期时间查询
ttl user
//修改user
set user zhangsan
//再次查询过期时间
ttl user
7. redis中数据list、hash、set、zset容器型数据结构的使用规则
1. create if not exists: 如果容器不存在,存放对象的元素的时候,创建一个数据结构再操作。
2. drop if no elements: 如果容器中最后一个元素被删除,会删除这个数据结构。