03、Redis常用五大数据类型

常用命令

命令大全地址
Redis命令十分丰富,包括的命令组有Cluster、Connection、Geo、Hashes、HyperLogLog、Keys、Lists、Pub/Sub、Scripting、Server、Sets、Sorted Sets、Strings、Transactions一共14个redis命令组两百多个redis命令。

# 启动客户端
./redis-cli
# 查看当前库所有key
keys * 
# 查看当前库以KK开头的key
keys kk**
# 判断某个key是否存在
exists key kk
# 查看key是什么类型
type kk
# 删除指定的key数据
del key kk
# 删除指定的key(s),若key不存在则该key被跳过。但是,相比DEL会产生阻塞,该命令会在另一个线程中回收内存,因此它是非阻塞的。 这也是该命令名字的由来:仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。
unlink key kk 
# key设置过期时间
expire kk 10
# 查看还有多少秒过期,-1表示永不过期,-2表示已过期
ttl kk
# 切换数据库
select 2
# 查看当前数据库的key的数量
dbsize
# 清空当前库
flushdb
# 清空所有库
flushall

五大基本数据类型

Redis不是简单的键值存储,它实际上是一个支持不同类型值的数据结构服务器。这意味着在传统键值存储中,您将字符串键与字符串值相关联,而在Redis中,该值不仅限于简单的字符串,还可以容纳更复杂的数据结构。以下是Redis支持的所有数据结构的列表:

  • Binary-safe strings
  • Lists
  • Sets
  • Sorted sets
  • Hashes
  • Bit arrays (or simply bitmaps)
  • HyperLogLogs
  • Streams

一、String字符串

Redis字符串类型是您可以与Redis键关联的最简单的值类型。由于Redis键是字符串,因此当我们也使用字符串类型作为值时,我们会将一个字符串映射到另一个字符串。值可以是每种类型的字符串(包括二进制数据),例如,您可以在值内存储jpeg图像。值不能大于512 MB。

常用命令

get/set

# 添加键值对,key已存在时,会覆盖
set mykey somevalue
# 只有在key不存在时,设置key的值,已存在key时,无法设置
setnx kk vv1sdsds
# 查询对应键值
get mykey
# 将给定的<value> 追加到原值的末尾
append kk hahaha999
# 获得值的长度
strlen kk
# 同时添加多个键值对
mset k1 v1 k2 v2 k3 v3
# 同时获取多个键值
mget k1 k2 
# 同时添加多个键值对,当其中某个KEY存在时,则都不会添加
msetnx k4 v4 k3 vv3
# 获得值的范围,从0开始,包含开始结束位置
getrange k1 0 2
# 从1位置开始覆盖k1的值为zxcv
setrange k1 1 zxcv
# 设置k1的值,并设置10秒后过期
setex k1 10 vv2
# 获取k1原来的值,并设置新的值为vvv1
getset k1 vvv1

incr/decr
redis原子性:Redis采用单线程+多路IO复用技术,所以Redis单个命令的执行都是原子性的,Redis命令的原子性使得我们不用考虑并发问题,可以方便的利用原子性自增操作INCR实现简单计数器功能。

INCR key: 对存储在指定key的数值执行原子的加1操作。如果指定的key不存在,那么在执行incr操作之前,会先将它的值设定为0再+1。如果指定的key中存储的值不是字符串类型(fix:)或者存储的字符串类型不能表示为一个整数,那么执行这个命令时服务器会返回一个错误(eq:(error) ERR value is not an integer or out of range)。这个操作仅限于64位的有符号整型数据。

DECR key: 对key对应的数字做减1操作。如果key不存在,那么在操作之前,这个key对应的值会被置为0再-1。如果key有一个错误类型的value或者是一个不能表示成数字的字符串,就返回错误。这个操作最大支持在64位有符号的整型数字。

incrby / decrby key num:将 key 中储存的数字值增减num。

# +1
incr inkey
# -1 
decr dekey
# +10
incrby inkey 10
# -100 
decrby dekey 100

String数据结构

不管在什么编程语言、存储引擎中,String都是应用最广泛的,而在不同的编程语言、存储引擎中,String可能有不同的实现,在Redis中,String的底层就是SDS,它的全称是Simple Dynamic String。

struct sdshdr {

    int len; // 存储字符串的实际长度
    int free; // 存储剩余(空闲)的空间
    char buf[]; // 存储实际数据
};

空间预分配:当对字符串进行拼接操作的时候,Redis会很贴心的分配一定的剩余空间,这块剩余空间现在看起来是有点浪费,但是我们如果继续拼接,这块剩余空间的作用就出来了。

惰性空间释放:当我们做了字符串缩减的操作,Redis并不会马上回收空间,因为你可能即将又要做字符串的拼接操作,如果你再次操作,还是没有用到这部分空间,Redis也会去回收这部分空间。

扩容策略: 字符串小于1M,采用的是加倍扩容的策略,也就是多分配100%的剩余空间,当大于1M,每次扩容,只会多分配1M的剩余空间。

最大长度: Redis 规定字符串的长度不得超过 512M 字节。

二、List列表

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边),一个列表最多可以包含 (2的32次方 - 1 )个元素 (4294967295, 每个列表超过40亿个元素)。

常用命令

LPUSH key value [value …]: 将所有指定的值插入到存于 key 的列表的头部。如果 key 不存在,那么在进行 push 操作前会创建一个空列表。 如果 key 对应的值不是一个 list 的话,那么会返回一个错误。

RPUSH key value [value …]:向存于 key 的列表的尾部插入所有指定的值。

LPOP key: 移除并且返回 key 对应的 list 的第一个元素,或者当 key 不存在时返回 nil。

RPOP key: 移除并返回存于 key 的 list 的最后一个元素。

RPOPLPUSH source destination: 原子性地返回并移除存储在 source 的列表的最后一个元素(列表尾部元素), 并把该元素放入存储在 destination 的列表的第一个元素位置(列表头部)

LRANGE key start stop: 返回存储在 key 的列表里指定范围内的元素。 start 和 end 偏移量都是基于0的下标,即list的第一个元素下标是0(list的表头),第二个元素下标是1,以此类推。

偏移量也可以是负数,表示偏移量是从list尾部开始计数。 例如, -1 表示列表的最后一个元素,-2 是倒数第二个,以此类推。

LINDEX key index: 返回列表里的元素的索引 index 存储在 key 里面。 下标是从0开始索引的,所以 0 是表示第一个元素, 1 表示第二个元素,并以此类推。 负数索引用于指定从列表尾部开始索引的元素。在这种方法下,-1 表示最后一个元素,-2 表示倒数第二个元素,并以此往前推。

当key 位置的值不是一个列表的时候,会返回一个error。

LLEN key : 返回存储在 key 里的list的长度。 如果 key 不存在,那么就被看作是空list,并且返回长度为 0。 当存储在 key 里的值不是一个list的话,会返回error。

LINSERT key BEFORE|AFTER pivot value: 把 value 插入存于 key 的列表中在基准值 pivot 的前面或后面。

当key 不存在时,这个list会被看作是空list,任何操作都不会发生。

当key 存在,但保存的不是一个list的时候,会返回error。

LREM key count value: 从存于 key 的列表里移除前 count 次出现的值为 value 的元素。 这个 count 参数通过下面几种方式影响这个操作:

  • count > 0: 从头往尾移除值为 value 的元素。
  • count < 0: 从尾往头移除值为 value 的元素。
  • count = 0: 移除所有值为 value 的元素。
    比如, LREM list -2 “hello” 会从存于 list 的列表里移除最后两个出现的 “hello”。

需要注意的是,如果list里没有存在key就会被当作空list处理,所以当 key 不存在的时候,这个命令会返回 0。

LSET key index value: 设置 index 位置的list元素的值为 value。 更多关于 index 参数的信息,详见 LINDEX。

当index超出范围时会返回一个error。

# 向list集合头部添加元素1
lpush list 1
# 向list集合尾部添加元素2
rpush list 2
# 移除list第一个元素并返回
lpop list
# 移除list最后一个元素并返回
rpop list
# 移除list最后一个元素并放入list1的第一位置
rpoplpush list list1
# 查看list0-3位置的元素
lrange list 0 3
# 获取list 0索引位置的元素
lindex list 0
# 查看list长度
llen list 
# 在list的v3元素后添加v4
linsert list after v3 v4
# 从尾到头开始,移除1个v1
lrem list -1 v1
# 替换0位置的元素为v0
lset list 0 v0

List数据结构

其底层有linkedList、zipList和quickList这三种存储方式。

当列表对象中元素的长度较小或者数量较少时,通常采用zipList来存储;当列表中元素的长度较大或者数量比较多的时候,则会转而使用双向链表linkedList来存储。

qucikList是由zipList和双向链表linkedList组成的混合体。它将linkedList按段切分,每一段使用zipList来紧凑存储,多个zipList之间使用双向指针串接起来。示意图如下所示:
&nbsp;
为了进一步节约空间,Redis还会对zipList进行压缩存储,使用LZF算法进行压缩,可以选择压缩深度。

三、Set集合

Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的

常用命令

SADD key member [member …]: 添加一个或多个指定的member元素到集合的 key中.指定的一个或者多个元素member 如果已经在集合key中存在则忽略.如果集合key 不存在,则新建集合key,并添加member元素到集合key中.如果key 的类型不是集合则返回错误。

SMEMBERS key: 返回key集合所有的元素。

SISMEMBER key member:返回成员 member 是否是存储的集合 key的成员.如果member元素是集合key的成员,则返回1,如果member元素不是key的成员,或者集合key不存在,则返回0。

SCARD key:返回集合存储的key的基数 (集合元素的数量).集合的基数(元素的数量),如果key不存在,则返回 0。

SREM key member [member …]:在key集合中移除指定的元素. 如果指定的元素不是key集合中的元素则忽略 如果key集合不存在则被视为一个空的集合,该命令返回0.如果key的类型不是一个集合,则返回错误,返回值为从集合中移除元素的个数。

SPOP key [count]:从存储在key的集合中移除并返回一个或多个随机元素。

SRANDMEMBER key [count]:随机从该集合中取出n个值。不会从集合中删除 。

SMOVE source destination member:将member从source集合移动到destination集合中. 如果该元素成功移除,返回1,如果该元素不是 source集合成员,无任何操作,则返回0。

SINTER key [key …]:返回指定所有的集合的成员的交集.如果key不存在则被认为是一个空的集合,当给定的集合为空的时候,结果也为空.(一个集合为空,结果一直为空)。

SUNION key [key …]:返回给定的多个集合的并集中的所有成员.

SDIFF key [key …]:返回一个集合与给定集合的差集的元素.

# set集合添加元素
sadd set v1 v2 v3 
# 查看set中的所有元素
smembers set
# 查看v4是不是set集合中的元素
sismember set v4
# 查看set中的元素个数
scard set 
# 移除set中的元素v1
srem set v1
# 随机移除并返回2个值
spop set 2
# 随机查看两个值,不删除
srandmember set 2
# 将set中的元素v2,移动到set1中
smove set set1 v2
# 查看set和set1中都存在的元素
sinter set set1 
# 查看set和set1中并集元素
sunion set set1
# 查看set中有,set1中没有的元素
sdiff set set1

Set数据结构

Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。

一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变。

Set数据结构是用哈希表实现的。在新增数据时会进行去重。
&nbsp;

四、Hash哈希

Redis hash 是一个键值对集合,是一个string类型的field和value的映射表,hash特别适合用于存储对象。类似Java里面的Map<String,Object>。
&nbsp;

常用命令

HSET key field value:设置 key 指定的哈希集中指定字段的值。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联。如果字段在哈希集中存在,它将被重写。

HGET key field: 返回 key 指定的哈希集中该字段所关联的值,当字段不存在或者 key 不存在时返回nil。

HMSET key field value [field value …]:设置 key 指定的哈希集中指定字段的值。该命令将重写所有在哈希集中存在的字段。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联。

HEXISTS key field:返回hash里面field是否存在,返回1,hash里面包含该field,0 表示hash里面不包含该field或者key不存在。

HKEYS key:返回 key 指定的哈希集中所有字段的名字。当 key 指定的哈希集不存在时返回空列表。

HVALS key:返回 key 指定的哈希集中所有字段的值。

HINCRBY key field increment:增加 key 指定的哈希集中指定字段的数值。如果 key 不存在,会创建一个新的哈希集并与 key 关联。如果字段不存在,则字段的值在该操作执行前被设置为 0,HINCRBY 支持的值的范围限定在 64位 有符号整数。

HSETNX key field value:只在 key 指定的哈希集中不存在指定的字段时,设置字段的值。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联。如果字段已存在,该操作无效果。

# 添加一个hash对象,并添加name和age
hset hashkey name zhangsan age 18
# 查看hash对象的name字段对应的值
hget hashkey name
# 添加一个user对象,并添加相关属性
hmset user name lisi age 18 sex 男
# 查看user对象是否有score属性
hexists user score
# 查看user所有属性
hkeys user
# 查看user所有属性值
hvals user
# 给user的age属性值+3
hincrby user age 3
# 给user添加score为90
hsetnx user score 90

Hash数据结构

其底层实现方式有两种,一种是zipList,这种是当hash结构的V值较小的时候使用的编码方式,较大时使用使用hashtable。

五、Zset(sorted set) 有序集合

Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。
不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。

因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。

访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

常用命令

ZADD key [NX|XX] [CH] [INCR] score member [score member …]: 将所有指定成员添加到键为key有序集合(sorted set)里面。 添加时可以指定多个分数/成员(score/member)对。 如果指定添加的成员已经是有序集合里面的成员,则会更新改成员的分数(scrore)并更新到正确的排序位置。

如果key不存在,将会创建一个新的有序集合(sorted set)并将分数/成员(score/member)对添加到有序集合,就像原来存在一个空的有序集合一样。如果key存在,但是类型不是有序集合,将会返回一个错误应答。

分数值是一个双精度的浮点型数字字符串。+inf和-inf都是有效值。

参数说明:

  • XX: 仅仅更新存在的成员,不添加新成员。
  • NX: 不更新存在的成员。只添加新成员。
  • CH: 修改返回值为发生变化的成员总数,原始是返回新添加成员的总数 (CH 是 changed 的意思)。更改的元素是新添加的成员,已经存在的成员更新分数。 所以在命令中指定的成员有相同的分数将不被计算在内。注:在通常情况下,ZADD返回值只计算新添加成员的数量。
  • INCR: 当ZADD指定这个选项时,成员的操作就等同ZINCRBY命令,对成员的分数进行递增操作。

ZRANGE key start stop [WITHSCORES]:返回存储在有序集合key中的指定范围的元素。 返回的元素可以认为是按得分从最低到最高排列。 如果得分相同,将按字典排序。

参数start和stop都是基于零的索引,即0是第一个元素,1是第二个元素,以此类推。 它们也可以是负数,表示从有序集合的末尾的偏移量,其中-1是有序集合的最后一个元素,-2是倒数第二个元素,等等。

start和stop都是全包含的区间,因此例如ZRANGE myzset 0 1将会返回有序集合的第一个和第二个元素。

可以传递WITHSCORES选项,以便将元素的分数与元素一起返回。这样,返回的列表将包含value1,score1,…,valueN,scoreN,而不是value1,…,valueN。 客户端类库可以自由地返回更合适的数据类型(建议:具有值和得分的数组或记录)。

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]: 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。

ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]:返回有序集合中指定分数区间内的成员,分数由高到低排序。

ZINCRBY key increment member:为有序集key的成员member的score值加上增量increment。如果key中不存在member,就在key中添加一个member,score是increment(就好像它之前的score是0.0)。如果key不存在,就创建一个只含有指定member成员的有序集合。

ZREM key member [member …]:删除该集合下,指定值的元素 。

ZCOUNT key min max:返回有序集key中,score值在min和max之间(默认包括score值等于min或max)的成员。

ZRANK key member:返回有序集key中成员member的排名。其中有序集成员按score值递增(从小到大)顺序排列。排名以0为底,也就是说,score值最小的成员排名为0。

# 添加一个zset,设置评分并添加元素
zadd zsetkey 1 one 2 two 3 three
# 查看0-3位置的值,并查看积分
zrange zsetkey 0 3 withscores
# 查看积分1-3的所有元素
zrangebyscore zsetkey 1 3
# 为one元素积分+2
zincrby zsetkey 2 one
# 删除元素one
zrem zsetkey  one
# 统计积分1-3 元素的个数
zcount zsetkey 1 3
# 查看two 元素的排名
zrank zsetkey two 

Zset数据结构

zset是Redis提供的一个非常特别的数据结构,常用作排行榜等功能,以用户id为value,关注时间或者分数作为score进行排序。与其他数据结构相似,zset也有两种不同的实现,分别是zipList和skipList。具体使用哪种结构进行存储,规则如下:

zipList:满足以下两个条件

  • [score,value]键值对数量少于128个;
  • 每个元素的长度小于64字节;
    &nbsp;

skipList:不满足以上两个条件时使用跳表、组合了hash和skipList

  • hash用来存储value到score的映射,这样就可以在O(1)时间内找到value对应的分数;
  • skipList按照从小到大的顺序存储分数
  • skipList每个元素的值都是[socre,value]对

&nbsp;
跳跃表(跳表)

有序集合在生活中比较常见,例如根据成绩对学生排名,根据得分对玩家排名等。对于有序集合的底层实现,可以用数组、平衡树、链表等。数组不便元素的插入、删除;平衡树或红黑树虽然效率高但结构复杂;链表查询需要遍历所有效率低。Redis采用的是跳跃表。跳跃表效率堪比红黑树,实现远比红黑树简单。

对比有序链表和跳跃表,从链表中查询出51

1、 有序链表,要查找值为51的元素,需要从第一个元素开始依次查找、比较才能找到共需要6次比较;
&nbsp;
2、 跳跃表:从第2层开始,1节点比51节点小,向后比较21节点比51节点小,继续向后比较,后面就是NULL了,所以从21节点向下到第1层在第1层,41节点比51节点小,继续向后,61节点比51节点大,所以从41向下在第0层,51节点为要查找的节点,节点被找到,共查找4次从此可以看出跳跃表比有序链表效率要高;
&nbsp;