C# list删除 另外list里面的元素_Redis#list列表(二)

443b7729e396cc0866653dff1dae4d22.png

首先介绍一下Redis#list的使用方法

Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)。

  • 当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。
  • Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。

右边进左边出:队列、右边进右边出:堆栈

e611715d22232e93f9a49cbd13bde78f.png

慢操作

  • lindex 相当于 Java 链表的 get(int index)方法,它需要对链表进行遍历,性能随着参数 index 增大而变差。
  • ltrim 跟的两个参数 start_index 和 end_index 定义了一个区间,在这个区间内的值, ltrim 要保留,区间之外统统砍掉(index=-1 表示倒数第一个元素,同样 index=-2 表示倒数第二个元素)

e1e5a00f0e0fba202822d7f4765e5727.png

快速列表

9700d76b539a33504a049b63828656f1.png

Redis 底层存储的还不是一个简单的 linkedlist,而是称之为快速链表 quicklist 的一个结构。

  • 在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
  • 当数据量比较多的时候才会改成 quicklist

Q:为什么采用这种设计方式?

A:因为普通的链表需要的附加指针空间太大,会比较浪费空间,而且会加重内存的碎片化。比如这个列表里存的只是 int 类型的数据,结构上还需要两个额外的。指针 prev 和 next 。所以 Redis 将链表和 ziplist 结合起来组成了 quicklist。也就是将多个 ziplist 使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

「压缩列表」内部

Redis 为了节约内存空间使用,zset 和 hash 容器对象在元素个数较少的时候,采用压缩列表 (ziplist) 进行存储。压缩列表是一块连续的内存空间,元素之间紧挨着存储,没有任何冗余空隙。

c5a785b185d600d3cfb0efae77f75146.png

f9bbc5b0c6847a7483cffeac8d66e429.png

63f2946ef0fb6fc6904066d267b7ca7c.png
  • 压缩列表为了支持双向遍历,所以才会有 ztail_offset 这个字段,用来快速定位到最后一个元素,然后倒着遍历。

entry 块随着容纳的元素类型不同,也会有不一样的结构。

42662920c6d146c123c587c1b75c1140.png
  • prevlen 字段表示前一个 entry 的字节长度,当压缩列表倒着遍历时,需要通过这个字段来快速定位到下一个元素的位置。

prelen它是一个变长的整数,当字符串长度小于254(0xFE) 时,使用一个字节表示;如果达到或超出 254(0xFE) 那就使用 5 个字节来表示。第一个字节是 0xFE(254),剩余四个字节表示字符串长度。

74a2c0f05578a9a3a1078ff5e8383213.png
  • encoding 字段存储了元素内容的编码类型信息,ziplist 通过这个字段来决定后面的 content 内容的形式。 例如:
  1. 00xxxxxx 最大长度位 63 的短字符串,后面的 6 个位存储字符串的位数,剩余的字节就是字符串的内容
  2. 01xxxxxx xxxxxxxx 中等长度的字符串,后面 14 个位来表示字符串的长度,剩余的字节就是字符串的内容
  3. 10000000 aaaaaaaa bbbbbbbb cccccccc dddddddd 特大字符串,需要使用额外 4 个字节来表示长度。第一个字节前缀是 10,剩余 6 位没有使用,统一置为零。后面跟着字符串内容。不过这样的大字符串是没有机会使用的,压缩列表通常只是用来存储小数据的。

增加元素

因为 ziplist 都是紧凑存储,没有冗余空间 。意味着插入一个新的元素就需要调用 realloc 扩展内存。

  • realloc 可能会重新分配新的内存空间,并将之前的内容一次性拷贝到新的地址
  • 也可能在原有的地址上进行扩展,这时就不需要进行旧内容的内存拷贝

如果 ziplist 占据内存太大,重新分配内存和拷贝内存就会有很大的消耗。所以 ziplist 不适合存储大型字符串,存储的元素也不宜过多。

级联更新

前面提到每个 entry 都会有一个 prevlen 字段存储前一个 entry 的长度。如果内容小于 254 字节,prevlen 用 1 字节存储,否则就是 5 字节。这意味着如果某个 entry 经过了修改操作从 253 字节变成了 254 字节,那么它的下一个 entry 的 prevlen 字段就要更新,从 1 个字节扩展到 5 个字节;如果这个 entry 的长度本来也是 253 字节,那么后面 entry 的 prevlen 字段还得继续更新。

注意:如果 ziplist 里面每个 entry 恰好都存储了 253 字节的内容,那么第一个 entry 内容的修改就会导致后续所有 entry 的级联更新,这就是一个比较耗费计算资源的操作。

「快速列表」内部

Redis 早期版本存储 list 列表数据结构使用的是压缩列表 ziplist 和普通的双向链表linkedlist,也就是元素少时用 ziplist,元素多时用 linkedlist。

考虑到链表的附加空间相对太高,prev 和 next 指针就要占去 16 个字节 (64bit 系统的 指针是 8 个字节),另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响内存管理效率。后续版本对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist。

aae431f9b3ca6b2dc388c6c6bfc1ac3a.png

quicklist 是 ziplist 和 linkedlist 的混合体,它将 linkedlist 按段切分,每一段使用 ziplist 来紧凑存储,多个 ziplist 之间使用双向指针串接起来。

79c60dc912102da822fd9115d90da5b5.png

bf261e9d1bf4c96df74b469ed70725b3.png

e6a3c74257416250c7313ed40173f4d5.png

为了进一步节约空间,Redis 还会对ziplist 进行压缩存储,使用 LZF 算法压缩,可以选择压缩深度。

每个 ziplist 存多少元素?

quicklist 内部默认单个 ziplist 长度为 8k 字节,超出了这个字节数,就会新起一个ziplist。ziplist 的长度由配置参数 list-max-ziplist-size 决定。

压缩深度

3263650c0d56de777c7540a6ea4cc713.png

quicklist 默认的压缩深度是 0,也就是不压缩。压缩的实际深度由配置参数 list-compress-depth 决定。为了支持快速的 push/pop 操作,quicklist 的首尾两个 ziplist 不压缩,度就是 1。如果深此时深度为 2,就表示 quicklist 的首尾第一个 ziplist 以及首尾第二个 ziplist 都不压缩。


IntSet 小整数集合

当 set 集合容纳的元素都是整数并且元素个数较小时,Redis 会使用 intset 来存储结合元素。intset 是紧凑的数组结构,同时支持 16 位、32 位和 64 位整数。

7f3d72acdd8f0dac21df31216b45cf3c.png

8ea895ec15b41b3c64da59f688a02a81.png

a0c3d085340dc5e038faf593dc9986ad.png

注意观察 debug object 的输出字段 encoding 的值,可以发现当 set 里面放进去了非整数值时,存储形式立即从 intset 转变成了 hash 结构。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值