List(列表)
各位好我是霜华,今天将一个在开发中很常用的一个数据结构(用于消息队列)
下面开讲!!!
基本定义:
有序可重复,类似LinkedList ,插⼊和删除块,复杂度O(1),索引定位很慢复杂度O(n)
是⼀个字符链表,内部结构类linkedList (双向链表),left,right 都可以插⼊添加,如果健不存
在,创建新的链表,如果健已存在,新增内容,
如果值全部移除,对应的健也就消失了(⼈话:容器没东⻄了,容器就销毁)
列表最多可存储2^32-1 元素(4294967295 每个类表可存储40多亿)
原理:
底层是⼀个“快速链表”(quicklist)的结构,在列表元素较少时,使⽤连续的内存存储成压缩列表
ziplist。当数据量较多时,改成quicklist,也是讲多个ziplist使⽤双向指针串起来使⽤,以减少内存的
碎⽚化。
详细原理:
list有两种形态:
ziplist(连续内存) 和 quicklist(链表和ziplist结合,一定程度提高了改的性能,也减少了空间占用问题,小孩子才做选择,性能和空间我都要)
list的结构和LinkList很像但它不是一味的照搬,因为LinkList确实增删改很快,但因为他一个元素有前元素地址、自己元素、后元素地址这导致
1、因为你需要一遍遍访问地址,它遍历实在是太慢了
2、我存一个元素实在太花空间了
这时候ziplist这种包裹状的数据结构在数据不是很多时候,很吃香。(为什么:跟我想,你把所有数据放进一个包里,他们地址都是连续的,你可以顺着那条线一个个找,少的时候还好说,数据多的时候呢,你怎么找)
zipList压缩列表:
在容器对象的元素葛素会用压缩列表进行存储,一个ziplist的底层是一块连续的内存空间没有任何空余(就是没有元素之外的信息(LinkLIst单位信息大)。或者空余(String 有空余空间))
压缩包压缩包之间就是靠着互有地址相互链接
ziplist 内部代码和示意图
struct ziplist<T>{
int32 zlbytes;
int32 zltail_offset;
int16 zllength;
T[]entries;
int8 zlend;
}
zltbyes://表示整个压缩列表的占用字节数
zltail_offset://压缩包最后一个元素距离压缩列表起始位置的偏移量,(包与包之间是双向链表,查找某个元素就不用遍历拉,快速定位定位到这个最后一个元素)(@linklist 学学人家学学!!!)
zllength:/元素个数
entries//元素内容列表,一次紧凑存储
zlend//标志压缩列表的结束,值恒为0xFF
单个entry的结构
struct entry{
int<var> prevlen;//前一个entry 的字节的长度
int<var> encoding;//元素类型编码
optional byte[] content//元素内容
}
prevlen //上一个entry 的字节长度,当倒着遍历时,可以之间定位到下个元素的位置,相当于有了它你就可以跳过去了,而不是顺着那根线找
当字符串长度小于254,prevlen用一个字节表示,超出254时候,用5个字节表示
encoding字段存储了元素内容的编码类型信息,ziplist通过这个来决定后面的content形式
redis如何根据encoding前缀为来区分内容
ziplist如何增加元素:
因为其紧凑存储,无冗余空间,每加入新元素就要扩容
可能会重新分布内存空间,将原来的数据copy过去,如果数据很大,增加元素会有很大内存消耗
quicklist:
以前redis的list 在元素多时候 用linklist存储,但链表附加空间太高了,两个指针占了16字节,而且元素之间在内存都是零散分布,之后quiccklist替代(ziplist,和linklist混合)
它将linkedlist 按段分,为异端用ziplist压缩,多一个ziplist之间用双向指针串起来
实现原码:
struct quicklistNode{
quicklistNode prev;
quicklistNode next;
ziplist* zl;//指向压缩列表
int32 size;//指向的字节总数
int16 count;//元素数量
int2 encoding;//存储
}
strcut quicklist{
quicklistNode head;
quicklistNode tail;
long count;//元素总数
int nodes;//ziplist节点个数
int compressDepth //LZF算法压缩深度
}
Redis可能会对ziplist进行又一次压缩(我真的一滴也不剩了)
叫压缩深度:
默认是0,不压缩
1:quicklist首位不压缩
2:quicklist首位第一个和第二个不压缩
(quicklist中的ziplist默认其长度位8kb)
*命令:
a、队列操作:
rpush: rpush list2 1 2 3 4 5(从右边存⼊数据)
lrange:遍历:lrange list2 0 -1(得到对应范围内的值,如上⾏得12345)(慢加载)
llen:查长:lien list2 (查看长度)
lpop:删:lpop list2(从左侧弹出,返回弹出的数据5)
b、栈操作:
rpush: rpush list2 1 2 3 4 5
rpop:删除) list2(从右侧弹出5)
lpush list 1 2 3 4 5 (从左边存⼊数据)(栈结构)
c、遍历操作:(不建议调用这个实在实在太慢了)
1、lindex :定点查:lindex liset2 n 获取某个位置的值,小角标从0开始(时间复杂度为0(n))
2、lren:看数删:lren list2 n m 从左边开始,如果值等于m就删除,直到删除数量到n
3、lset+index:set list n m 吧第n位变成m(更改指定位置的值)(n从0开始算起)//像集合的set方法
4、linsert+before/after:linsert list after n m 在n这个数的后⾯插⼊m、linsert list before n m 在n这个数的前⾯插⼊m。
5、ltrm :截取:ltrim list2 n m 从n到m截取出来,其他东⻄不要了
(注:很多内容皆来自 《Redis深度历险 核心原理与应用实践》这本书)
至于与它相关的消息队列我会抽一期来单独讲,今天先到着我们下期见!