Redis数据结构基础教程:string、list、hash、set和zset
Redis有5种基本数据结构,string、list、hash、set和zset。它们是日常开发中最常用的数据结构。如果你彻底理解了这五种数据结构,你就掌握了Redis应用数据的一半。
string
我们先从string开始吧。 string 表示一个可变字节数组。我们初始化字符串的内容,获取字符串的长度,获取string子字符串,替换string子字符串的内容,并添加子字符串。
Redis 字符串是动态字符串,可以编辑。内部结构类似于Java的ArrayList。它使用预先分配的冗余空间来减少频繁的内存分配,如图所示。内部为当前字符串保留的空间容量通常大于实际字符串长度len。当字符串长度小于1M时,扩展将现有空间增加一倍。如果超过100万,插件一次只会扩大空间100万。需要注意的是,字符串的最大长度为512M。
初始化字符串 必须输入“变量名称”和“变量内容”
> set ireader beijing.zhangyue.keji.gufen.youxian.gongsi
OK
复制代码
检索字符串内容 输入“变量名称” 输入“变量名称”
> strlen ireader
(integer) 42
复制代码
获取子字符串 输入“变量名称”和开始和结束位置 [开始,结束]
> getrange ireader 28 34
"youxian"
复制代码
替换子字符串 变量位置和子字符串 追加子字符串
> append ireader .hao
(integer) 46 # 返回长度
> get ireader
"beijing.zhangyue.keji.gufen.wooxian.gongsi.hao"
复制代码
不幸的是,字符串不提供添加和删除字符串的方法。
计数器 如果字符串的内容是整数,则该字符串也可以用作计数器。
> set ireader 42
OK
> get ireader
"42"
> incrby ireader 100
(integer) 142
> get ireader
"142"
> decrby ireader 100
(integer) 42
> get ireader
"42"
> incr ireader # 等价于incrby ireader 1
(integer) 43
> decr ireader # 等价于decrby ireader 1
(integer) 42
复制代码
计数器有范围,不能超过Long.Max值或小于Long.MIN
> set ireader 9223372036854775807
OK
> incr ireader
(error) ERR increment or decrement would overflow
> set ireader -9223372036854775808
OK
> decr ireader
(error) ERR increment or decrement would overflow
复制代码
过期和删除可以使用del命令主动删除字符串,可以设置过期时间过期命令。积分会自动移除,属于被动移除。您可以使用 ttl 指令来获取字符串的生命周期。
> expire ireader 60
(integer) 1 # 1表示设置成功,0表示变量ireader不存在
> ttl ireader
(integer) 50 # 还有50秒的寿命,返回-2表示变量不存在,-1表示没有设置过期时间
> del ireader
(integer) 1 # 删除成功返回1
> get ireader
(nil) # 变量ireader没有了
复制代码
list
Redis将列表数据结构命名为list而不是table,因为列表存储结构使用链表而不是数组,并且链表也是双向链表。因为是链表,所以随机定位性能较差,头尾插入和删除性能较好。如果链表的长度很长,我们在使用时要注意与链表相关的操作的时间复杂度。
负下标链表元素的位置用自然数0,1,2,....n-1
表示。也可以使用负数 -1,-2。 ..-n
表示,-1
表示“最后到最后”,-2
表示“倒数第二”,那么表示第一个元素和对应的下标是0
。
队列/堆栈链表可以在数组的头部和尾部添加和删除元素。通过rpush/rpop/lpush/lpop这四个指令一起使用,链表可以从左到右用作队列或堆栈。是的
# 右进左出
> rpush ireader go
(integer) 1
> rpush ireader java python
(integer) 3
> lpop ireader
"go"
> lpop ireader
"java"
> lpop ireader
"python"
# 左进右出
> lpush ireader go java python
(integer) 3
> rpop ireader
"go"
...
# 右进右出
> rpush ireader go java python
(integer) 3
> rpop ireader
"python"
...
# 左进左出
> lpush ireader go java python
(integer) 3
> lpop ireader
"python"
...
复制代码
在日常应用中,列表经常被用作异步队列。
Length 使用 llen 指令获取链表的长度
> rpush ireader go java python
(integer) 3
> llen ireader
(integer) 3
复制代码
随机读取 可以使用 lindex 指令访问指定位置的元素,使用 lrange 指令。链表的子元素,并提供start和end子索引参数
> rpush ireader go java python
(integer) 3
> lindex ireader 1
"java"
> lrange ireader 0 2
1) "go"
2) "java"
3) "python"
> lrange ireader 0 -1 # -1表示倒数第一
1) "go"
2) "java"
3) "python"
复制代码
使用lrange获取所有元素时,必须提供end_index。如果没有负子索引,则必须先使用 llen 语句获取长度,然后才能获取 end_index 值。如果使用负下标,请使用 -1 而不是 end_index 来获得相同的结果。影响。
编辑元素 使用 lset 命令编辑指定位置的元素。
> rpush ireader go java python
(integer) 3
> lset ireader 1 javascript
OK
> lrange ireader 0 -1
1) "go"
2) "javascript"
3) "python"
复制代码
插入元素 使用 linsert 命令在列表中间插入元素。有经验的程序员都知道,当插入元素时,我们常常不知道应该插入指定位置之前还是之后,因此 antirez 是 linsert 指令中添加 before/after 方向参数,以显示插入前和插入后。然而令人意想不到的是,linsert语句并不是通过指定位置来插入的,而是通过指定具体值来插入的。这是因为在分布式环境中,列表的元素总是频繁变化,这意味着上一时刻计算出的元素的下标可能不是你期望的下一时刻的下标。
> rpush ireader go java python
(integer) 3
> linsert ireader before java ruby
(integer) 4
> lrange ireader 0 -1
1) "go"
2) "ruby"
3) "java"
4) "python"
复制代码
到目前为止我还没有发现任何特殊的应用场景可以添加到实际应用中。
删除元素列表删除功能不通过指定下标来指定元素。需要指定最大删除次数和元素值
> rpush ireader go java python
(integer) 3
> lrem ireader 1 java
(integer) 1
> lrange ireader 0 -1
1) "go"
2) "python"
复制代码
定长列表在实际应用场景中,我们有时会面临“定长列表”的需求。例如,如果要以旋转门的形式实时显示中奖用户列表,由于中奖用户太多,显示的数量通常不会超过100,所以这里使用定长列表。维护固定长度列表的指令是ltrim。必须提供start和end两个参数,表示要保留列表的下标间距,并删除所有超出范围的元素。
> rpush ireader go java python javascript ruby erlang rust cpp
(integer) 8
> ltrim ireader -3 -1
OK
> lrange ireader 0 -1
1) "erlang"
2) "rust"
3) "cpp"
复制代码
如果定义的参数末尾对应的实际下标小于开头,则效果与del命令相同,因为这样的参数表明列表元素的下标区间必须保持为空。
快速列表
如果你深入一点,你会发现Redis的最底层存储的并不是简单的链表,而是一种称为快速链表的结构。首先,当列表元素较小时,使用持久内存。这个结构是一个ziplist,它是一个压缩列表。它将所有元素存储在一起并分配持久内存。当数据量比较大时,转换为快速列表。因为常规链表需要的额外指针空间太大,浪费了空间。例如,这个列表只存储int类型的数据,并且该结构需要两个额外的指针,previous和next。所以Redis将链表和ziplist组合成一个快速列表。换句话说,多个 zip 使用双向指针连接在一起。这样既满足了快速插拔能力,又不会造成过多的空间冗余。
hash
Hash对应于Java中的HashMap或Python中的听写。在实现结构上,采用的是二维结构。第一维是数组,第二维是链表。哈希值的内容是键和值,存储在一个链表中,数组存储链表的父指针。通过key查找元素时,首先计算key的哈希码,然后利用哈希码调制数组的长度来定位链表的末尾,然后利用链表获取对应的值。链表的任务是创建。 “哈希碰撞”元素聚集在一起。 Java开发者会感觉很熟悉,因为这个结构和HashMap没有什么区别。分布式表第一维的长度也是2^n。
添加元素可以使用hset命令一次添加一个键值对,也可以使用hmset工具一次添加多个键值对
> hset ireader go fast
(integer) 1
> hmset ireader java fast python slow
OK
复制代码
查找元素可以使用hget要查找特定键对应的值,可以使用 hmget 函数 Multiple 获取键对应的值,可以使用 hgetall 获取所有键值对,可以使用 hkeys 和 hvals 获取所有列表分别是键和值列表。这些功能类似于Java语言的地图接口。
> hmset ireader go fast java fast python slow
OK
> hget ireader go
"fast"
> hmget ireader go python
1) "fast"
2) "slow"
> hgetall ireader
1) "go"
2) "fast"
3) "java"
4) "fast"
5) "python"
6) "slow"
> hkeys ireader
1) "go"
2) "java"
3) "python"
> hvals ireader
1) "fast"
2) "fast"
3) "slow"
复制代码
删除元素 您可以使用 hdel 命令删除指定的键。 hdel 支持同时删除多个key
> hmset ireader go fast java fast python slow
OK
> hdel ireader go
(integer) 1
> hdel ireader java python
(integer) 2
复制代码
判断某个元素是否存在 一般我们使用hget命令来查找key对应的value是否为空,直到对应的元素存在,但是如果长度value字符串特别大,这样判断元素是否存在有点没有意义。在这种情况下,可以使用 hexists 命令。
> hmset ireader go fast java fast python slow
OK
> hexists ireader go
(integer) 1
复制代码
计数器 哈希结构也可以用作计数器,每个内部密钥可以用作独立的计数器。如果该值不是整数,调用hincrby语句将导致错误。
> hincrby ireader go 1
(integer) 1
> hincrby ireader python 4
(integer) 4
> hincrby ireader java 4
(integer) 4
> hgetall ireader
1) "go"
2) "1"
3) "python"
4) "4"
5) "java"
6) "4"
> hset ireader rust good
(integer) 1
> hincrby ireader rust 1
(error) ERR hash value is not an integer
复制代码
膨胀当密封件内的元件已满时(密封件碰撞很常见),需要膨胀。扩展需要检索一个新的 double 数组,并将所有键值对重新分配到与新数组索引对应的链表中(重新散列)。如果哈希结构很大,例如数百万个键值对,则完整的重新验证过程将需要很长时间。这对于单线程的Redis来说有点压力。这就是 Redis 实施渐进式重新检查解决方案的原因。它同时保留旧的和新的哈希结构,并在后续的调度任务和哈希结构读写指令中增量地将旧结构的元素转移到新结构。这样就可以避免扩容带来的线程延迟。
收缩 Redis 哈希结构不仅会扩展,还会收缩。从这一点来看,它比Java HashMap的效率更高,Java HashMap只是进行扩展。收缩的原理与扩展相同,只不过新表的大小是旧表大小的两倍。
set
Java程序员都知道HashSet的内部实现使用的是HashMap,但是所有的值都指向同一个对象。 Redis 的序列化结构也是如此。它内部也使用了哈希结构,所有值都指向同一个内部值。
添加元素可以一次添加多个元素
> sadd ireader go java python
(integer) 3
复制代码
读取元素使用members列出所有元素,使用scard获取数组长度,使用srandmember元素获取随机数。如果没有给出 count 参数,则默认为 1
> sadd ireader go java python
(integer) 3
> smembers ireader
1) "java"
2) "python"
3) "go"
> scard ireader
(integer) 3
> srandmember ireader
"java"
复制代码
移除元素 使用 srem 命令移除一个或多个元素,使用 spop 命令移除随机元素
> sadd ireader go java python rust erlang
(integer) 5
> srem ireader go java
(integer) 2
> spop ireader
"erlang"
复制代码
判断元素是否存在 seismi 使用命令只能接收一个元素 SortedSet(zset)是Redis提供的一种非常特殊的数据结构。一方面对应Java数据结构 Zset 的底层实现使用了两种数据结构。第一个是哈希,第二个是跳转列表。哈希的作用就是将元素的值和质心结合起来,保证元素值的唯一性。通过元素的值点值可以找到对应的元素。跳转列表的目的是对元素值进行排序,根据分数得到元素列表。 更多元素 您可以使用 zadd 命令添加一个或多个值/分数对,因此这些点将放置在前面 长度 zset 命令中的元素数量可以通过以下命令找到zset 命令。 删除元素 可以使用 zrem 命令删除 zset 中的元素,并且可以删除多个 计数器 与哈希结构一样,zset 也可以用作计数器。 获取排名和分数通过zscore命令获取指定元素的权重,通过zrank命令获取指定元素的优先级,通过[从第一个到最后一个]获取指定元素的倒序排名] zrevrank 命令。从小到大为正方向,由大到小为负方向。 根据rank范围获取元素列表 通过zrange命令指定rank范围参数,获取对应的元素列表,使用grade参数获取元素权重。使用 zrevrange 命令获取负[反向]顺序的元素列表。从小到大为正方向,由大到小为负方向。 按分数检索列表使用 zrangebyscore 指令指定分数以获取相应的元素列表。通过 zrevrangebyscore 指令获取反向元素列表。从小到大为正方向,由大到小为负方向。参数 因为zset需要支持随机插入和删除,所以用数组来表示并不容易。首先,我们看一下链表的标准结构。 这个链表必须按分数排序。这意味着当需要添加新元素时,需要将插入点放置在特定位置,以便链表可以进一步排序。通常,我们通过二分查找来找到插入点,但是二分查找的对象必须是一个数组。只有表可以支持快速定位,而链表则不能,那怎么办呢? 想想一家初创公司。一开始只有几个人。团队中的每个人都是平等的,都是创始人。随着公司规模的扩大,人数逐渐增多,团队沟通的成本也随之增加。目前正在实行班组长制度,对队伍进行拆分。每个小组都有一名小组长。会议以小组形式召开,几位组长有自己的会议安排。随着公司规模的扩大,需要增加另一个层次——部门。各部门从组长名单中推选一名代表担任部长。部长们之间也有自己的高层会议安排。 跳转列表类似于这种分层系统,最低层的所有元素都绑定在一起。然后为每个元素选择一个委托,并使用二级指针连接这些委托。然后从这些代表中选出二级代表,合并在一起。最终形成金字塔结构。 想想你的家乡在世界地图上的位置:亚洲-->中国->安徽省->安庆市->枞阳县->塘沟镇->天健村->天健村。 xxxx,也是类似的结构。 “跳转列表”之所以“跳转”,是因为内部元素可以“扮演多种角色”。例如,上图中中间的元素同时位于 L0、L1 和 L2 层中,并且可以在不同层之间快速移动。进行“跳跃”。 放置插入点时,首先将其放置在顶层,然后向下潜到下一个放置层,然后再向下潜到底层找到合适的位置并添加新元素。你可能会问,新添加的元素怎么能具有“身兼多职”的能力呢? 转职列表采用随机策略来决定新元素可以在哪一层兼职。首先,L0必须是100%,L1只有50%的机会,L2只有25%的机会,L3只有12.5%的机会。直到顶层 L31 的概率都是随机的。大多数元素不超过几层,只有少数元素深入顶层。列表中的元素越多,能够穿透的层次就越深,达到顶级的概率就越高。 这很公平。能不能进中央,不是看你的竞争,而是看运气。 作者:老钱> sadd ireader go java python rust erlang
(integer) 5
> sismember ireader rust
(integer) 1
> sismember ireader javascript
(integer) 0
复制代码
sortedset
Map
,可以给每个元素值分配一个权重score
,另一方面也类似到TreeSet
。内部元素根据优先级排序。可以得到各个元素的排名,也可以通过分数得到该元素。列表。 > zadd ireader 4.0 python
(integer) 1
> zadd ireader 4.0 java 1.0 go
(integer) 2
复制代码
> zcard ireader
(integer) 3
复制代码
> zrem ireader go python
(integer) 2
复制代码
> zadd ireader 4.0 python 4.0 java 1.0 go
(integer) 3
> zincrby ireader 1.0 python
"5"
复制代码
> zscore ireader python
"5"
> zrank ireader go # 分数低的排名考前,rank值小
(integer) 0
> zrank ireader java
(integer) 1
> zrank ireader python
(integer) 2
> zrevrank ireader python
(integer) 0
复制代码
> zrange ireader 0 -1 # 获取所有元素
1) "go"
2) "java"
3) "python"
> zrange ireader 0 -1 withscores
1) "go"
2) "1"
3) "java"
4) "4"
5) "python"
6) "5"
> zrevrange ireader 0 -1 withscores
1) "python"
2) "5"
3) "java"
4) "4"
5) "go"
6) "1"
复制代码
-inf
表示负无穷大,+inf
表示正无穷大。 ?它是使用非常具体和复杂的结构来实现的。读者必须对本节内容的深度做好心理准备。
链接:https://juejin.im/post/5b53ee7e5188251aaa2d2e16
版权所有。版权属于作者♷。商业转载请联系作者获得许可。非商业转载请注明出处。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。