Redis 数据类型


 

基础数据类型及其使用场景

redis是键值数据库,存储的数据都是键值对的形式,key只能是string类型,value可以为不同的类型,redis的5种基础数据类型如下
 

1、string  字符串

  • 最简单的数据类型,用于简单的键值存储,常用于实现分布式锁、分布式session、缓存、限流、计数器等。
  • 对象都可以转换为json字符串进行存储,理论上string可以存储所有类型的对象,但不推荐这样做,尽量选择合适的类型

 

2、hash  哈希

  • 存储对象,字段名不能重复,可以直接操作hash中的键值对,常用于存储需要直接操作字段的对象(这个对象没有嵌套其它对象)

 

3、list  列表

  • 元素有序、可重复,常用于实现消息队列、有序列表、存储有序的事件

 

4、set  集合

  • 元素无序、不可重复,常用于社交领域的去重、求交集
  • eg. 存储好友列表、存储点赞了这条朋友圈的好友,列出共同好友

 

5、sorted set  有序集合,也叫做zset

  • 有序、元素不能重复、分数可以重复,常用于实现排行榜、带权重的无序队列
  • eg. 微博热度排行、热销商品、歌曲排行榜、阅读排行榜、评论排行榜,带优先级的抢票

 

说明

  • string是基础数据类型,其它4种都是数据结构,根据类型特点来使用
  • hash可存储 2^32 - 1 (42亿+)个字段,list、set、zset均可存储 2^32 - 1 (42亿+)个元素
  • hash的字段值,list、set、zset的元素都只能是string,redis中只能使用这些数据类型。string虽然只能存储字符串,但二进制数据可以序列化为字符串,也就说string其实可以存储所有类型。使用hash存储对象时需要先序列化对象,从redis获取对象后需要反序列化得到对象。
  • set、zset,集合中的元素都是不可重复的

 

常用命令

string
set name "chy"  #单双引均可
set age 20  #redis没有数值类型,值默认是字符串类型,引不引都行。习惯上,数值型不引,其它字符串引一下


strlen name  #返回字符串(值)长度

append name "01" #在值的末尾添加字符串,返回操作后字符串(值)的长度。若key不存在,会先创建初始化为空串


incr age  #值自增+1,返回操作后的值。如果key不存在,会先新建初始化为0。常用于主键自增,比如订单编号。
incrby age 5  #指定增量,+5,增量只能是整数
incrbyfloat age 5.0  #增量可以是浮点数,

decr age  #值自减-1
decrby age  5  #指定减量,即-5


getrange name 0 2  #返回值在[0,2]上的字符串。redis的区间都是闭区间,支持负数索引

incr、decr常用于自增、自减的统计,比如访问量,点赞、收藏数等

 

hash
hset user name "chy"  #若user不存在,会自动创建。若name字段不存在,添加name字段并返回1;若name已存在,则更新其值,返回0
hsetnx user name "chy"  #若name字段已存在,则不进行任何操作
hmset user name "chy" age 20  #同时添加|更新多个字段


hget user name  #获取字段的值
hmget user name age #获取多个字段的值


hkeys user  #获取所有的字段名
hvals user  #获取所有的字段值


hdel user name  #删除指定字段,返回实际删除的字段个数
hdel user name age  #删除多个字段


hincrby user age 5  #值自增,指定增量,+5
hincrby user age -2  #负值表示值自减,-2

hincrbyfloat user age 5.0  #增量是浮点数
hincrbyfloat user age -2.0


hexists user name  #判断指定字段是否存在,存在返回1,不存在返回0

hlen user  #获取字段数

 

list
lpush users "zhangsan" "lisi"  #在list的头部插入一个或多个元素,l即left。若users不存在,会先自动创建。先插入"zhangsan",再插入"lisi",每次都是在最前面插入,list第一个元素是lisi
lpushx users "zhangsan" "lisi"  #如果users不存在,不作任何操作,不会自动创建

rpush users "wangwu" "zhaoliu"  #在list的尾部插入一个或多个元素,r即right,每次都是在最后面插入,list的最后一个元素是zhaoliu
rpushx users "wangwu" "zhaoliu"  #如果users不存在,不作任何操作,不会自动创建


lpop users  #弹出list的第一个元素,弹出是指返回并删除
rpop users  #弹出list的最后一个元素


blpop users 60  #弹出第一个元素,若list是空的,会阻塞等待60s,这60s内list内有了元素就弹出。60是超时时间。b即block,阻塞
brpop users 60  #最后一个元素


rpoplpush list1 list2  #弹出list1的最后一个元素,并在list2的头部插入该元素。即把列表的最后一个元素剪切到另一个列表的开头。
brpoplpush list1 list2 60  #如果list1是空的,阻塞等待60s


llen users  #返回list的长度(元素个数),第一个l即list

lrange users 0 -1  #返回[0,-1]上的所有元素

lindex users 1  #返回指定位置上元素

lset users 1 "zhangwei"  #修改指定位置上元素,该位置要已存在元素,才能修改,否则报错


lrem users 0 "zhangsan"  #移除list中所有值为zhangsan的元素。0表示删除匹配到的所有元素
lrem users 2 "zhangsan"  #从前往后搜索,删除匹配到的前2个元素
lrem users -2 "zhangsan"  #从后往前搜索,删除匹配到的前2个元素。正负表示搜索方向,数值表示删除个数。


ltrim users 1 3  #修剪list,只保留[1,3]上的元素,删除其它元素


linsert users before "zhangsan" "lisi"  #从前往后搜索"zhangsan",在匹配的第一个"zhangsan"前面插入元素lisi"。若"zhangsan"不存在,不做任何操作,返回-1;若users不存在,不做任何操作,返回0    
lisert users after "zhangsan" "lisi"  #后面插入

 

set
sadd users "zhangsan" "lisi"  #向集合中添加一个或多个元素,若users不存在,会自动创建。若set中已有该元素,则不添加该元素,返回本次操作添加的元素个数
srem users "zhangsan" "lisi"  #删除set中的一个或多个元素,若set中没有该元素,自动跳过


smembers users #返会set中的所有元素

sismember users "zhangsan"  #检测指定元素是否是users的成员,是返回1,不是返回0

scard users  #返回set中的元素个数


spop users  #随机弹出一个元素。set是无序的,只能随机弹,不能指定弹出哪个元素

srandmember users  #随机返回一个元素。是返回,不是弹出,不会删除该元素
srandmember users 3  #随机返回3个元素。随机返回一个元素,再从剩下的元素中随机返回一个,再从剩下的元素中随机返回一个,以此类推。就是说返回的元素各不相同。若数值大于集合的元素总数,则返回集合中所有的元素
srandmember users -3  #负数表示每次都从整个集合中选,不是从剩下的里面选,即返回的元素可能有相同的


smove set1 set2 "zhangsan" #把set1中的元素"zhangsan"剪切到set2中


sinter set1 set2 set3  #返回这些集合的交集(共同元素)
sinterstore destset set1 set2 set3  #求交集,并把交集中的元素存储到集合destset中。返回的是交集中的元素个数

sunion set1 set2 set3 #并集(合并到一起)
sunionstore destset set1 set2 set3 

sdiff set1 set2 #差集(set1中有、set2中没有的元素)
sdiffstore destset set1 set2

 

zset

zset中的每个元素都会关联一个分数,分数可用于实现榜单等排序相关功能,比如头条热度、歌曲热榜。

zset底层使用压缩表+跳表实现,所谓表指的是双向链表,按分数升序排列,插入元素时根据分数确定在链表中的存储位置。压缩表包含了全部元素,在压缩表的基础同时维护了多个跳表,需要在指定分数区间上操作时,通过跳表可快速转到指定区间上。

zadd grade 90 "zhangsan" 98.5 "lisi"  #添加一个或多个元素。分数在前,元素在后。分数支持浮点型、负数。如果元素已存在,则更新对应的分数。返回此次操作添加的元素个数

zrangebyscore grade 90 100  #返回分数在[90,100]上的所有元素,返回默认不带分数
zrangebyscore grade 90 100 withscores  #返回时带上对应的分数

zscore grade "zhangsan"  #返回关联的分数

zincrby grade 5 "zhangsan"  #将指定元素关联的分数+5
zincrby grade -5 "zhangsan"  #负数即减,-5


zrange grade 0 9  #返回[0,9]上的所有元素(取前十名),默认按分数升序排列
zrange grade 0 9 withscores  #返回时带上对应的分数
#zrevrange用法同上,只不过是按分数降序排列


zrank grade "zhangsan"  #返回该元素所在位置的下标(查询排名),默认按分数升序排列。
#zrevrank用法同上,只不过按分数降序排列


zcount grade 90 100 #返回分数在[90,100]上的元素个数
zcard grade  #返回元素总数


zrem grade "zhangsan" "lisi"  #删除一个或多个元素   
zremrangebyrank grade 0 2  #删除[0,2]上的所有元素,默认按分数升序排雷
zremrangebyscore grade 90 100  #删除分数在[90,100]上的所有元素

不带rev的是按分数升序排列,带rev的表示反序,按分数降序排列。

 

数据类型选择的建议

理论上所有的值都可以序列化为string进行存储,但不要全都存储为string类型,尽量结合类型特点、业务场景选择合适的数据类型。

尽量不要设置大量的key,大量的key不好管理、维护,且容易出现key命名冲突,比如尽量不要一个user对象对应一个key。

 

底层数据结构 | 基础数据类型的实现原理

常见数据类型的底层结构
数据类型限制底层数据结构
string大小上限 512 MBSDS
hash键值对最大个数2^32-1dict、ziplist(小集合)
list元素最大个数2^32-1quicklist
set元素最大个数2^32-1dict、intset(小整数集)
zset元素最大个数2^32-1dict+skiplist、ziplist(小集合)
bitmap大小上限 512 MBSDS

 

SDS

redis把c语言传统的字符串只用于字符串字面量,用在一些无须修改字符串本身的地方, 比如打印日志。redis构建了一种简单动态字符串(simple dynamic string,SDS),作为redis的字符串类型。

C字符串SDS
获取字符串长度的复杂度为 O(n)获取字符串长度的复杂度为 O(1)
API 是不安全的,可能会造成缓冲区溢出API 是安全的,不会造成缓冲区溢出
修改字符串长度 n 次必然要执行 n 次内存重分配修改字符串长度 n 次最多需要执行 n 次内存重分配
只能保存文本数据可以保存文本或二进制数据

SDS进行修改操作时,会先检查SDS的空间是否满足修改后所需的空间,空间不足时会自动扩容, 然后才执行修改操作,不存在应用操作时SDS所需空间不足的情况,即不会出现SDS缓冲区溢出。

SDS采用空间预分配、惰性空间释放,减少了修改字符串带来的内存重分配次数。

 

dict

dict是基于哈希表算法实现的一个数据结构,和传统哈希算法类似,dict使用哈希函数对key进行计算,得到在哈希表中的存储位置,采用拉链法解决hash冲突,在负载达到阈值(容量*负载因子)时进行重哈希扩容。

dict最显著的特点是重哈希采用增量式重哈希(incremental rehashing),在扩容时避免一次性对所有的 key 进行重哈希,而是将重哈希操作分散到对dict 的各个增删改查操作中去,每次只对一小部分 key 进行重哈希,而每次重哈希之间不影响 dict 的操作,避免了重哈希期间处理单个请求耗时长的问题。

为了实现增量式重哈希,dict中包含两个哈希表,在重哈希期间,数据从一个哈希表迁移到另一个哈希表。

可以把dict看成redis中的哈希表实现,哈希表的一大特点是尽量不存储重复元素,以减少hash冲突、提高性能,hash键不重复,set、zset元素不重复,这3种数据类型的实现都用到了dict。

 

ziplist

ziplist是一个经过特殊设计的双向链表,设计目标是为了节省存储占用的内存空间。

普通的双向链表,链表中每个节点都占用单独的一块内存,节点之间用指针(或引用)连接,这种方式会产生大量的内存碎片,且存储地址指针也会占用额外的内存空间。

ziplist 是list,但其实并不是linked list(链表),ziplist

  • 使用连续的地址空间来存储节点,本身能维护节点插入顺序,每个节点无需存储前后节点的指针,减少了内存占用;
  • 一个ziplist占用一大块内存,减少了内存碎片的产生;
  • 但增删节点需要移动很多节点,尤其在节点数量很多的情况下,增删节点效率低下,不适合频繁增删节点的情景。

ziplist可用于存储字符串或整数,其中整数是按二进制形式进行存储,并非存储为字符串。ziplist采用变长编码的方式储存值,存储节点值占用的空间大小不相同,大的整数会多用一些字节来存储,小的整数会少用一些字节来存储。

 

quicklist

quicklist 是由 ziplist 为节点组成的双向链表,这样设计是空间、时间(性能)上的折中,ziplist减少内存占用,双向链表可快速进行元素的增删操作。

 

skiplist 跳跃表

在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值