redis数据类型之list(列表)

文章目录

  • 一、队列
  • 二、栈
  • 三、其他操作
  • 四、内部实现
    • 压缩表(ziplist)
    • 快速列表

list是链表不是数组,故其插入和删除操作非常快,时间复杂度为O(1),但是索引定位很慢,时间复杂度为O(n)。

一、队列

> rpush numbers one two three
(integer) 3
> llen numbers
(integer) 3
> lpop numbers
"one"
> lpop numbers
"two"
> lpop numbers
"three"
> lpop numbers
(nil)

队列是先进先出的数据结构,常用语消息排队和异步逻辑处理。

二、栈

> rpush numbers one two three
(integer) 3
> rpop numbers
"three"
> rpop numbers
"two"
> rpop numbers
"one"
> rpop numbers
(nil)

栈是先进先出的数据结构。用reids列表数据结构来做栈的业务场景并不多见。

三、其他操作

这里的其他操作都是操作效率比较低的操作
1、lindex
可以使用lindex对列表进行遍历,但是其性能将随着参数lindex的增大而变差

> rpush numbers one two three
(integer) 3
> lindex numbers 1
"two"
> lindex numbers -1              #-1表示倒数第一个元素,-2表示倒数第二个元数
"three"

2、lrange
可以使用lrange取出start_index到end_index间的元素。

> rpush numbers one two three
(integer) 3
> lrange numbers 1 -1
"two"
"three"

3、ltrim
可以使用ltrim来保留start_index到end_index间的值,区间外的统统被砍掉。故使用ltrim可以实现一个定长的链表。

> rpush numbers one two three
(integer) 3
> ltrim numbers 1 -1
OK
> lrange numbers 0 -1
"two"
"three"
> ltrim numbers 1 0       #这其实是清空了整个列表,因为区间范围长度为负
OK
> llen numbers
(integer) 0

四、内部实现

redis 的 list的底层存储并不是一个简单的双向链表,而是一个称为“快速链表”(quicklist)的结构。该结构一般如下:
在这里插入图片描述
ziplist,即压缩表,是一块连续的内存,其中的所有元素是批次紧挨着的。其中的数据量是比较少的,当数据量比较大时就是多个ziplist,然后彼此间用双向指针串起来。这样就是quicklist结构。
该结构的优势为:
1、减少了内存的碎片化。
2、当数据比较少时,节省了指针的开销。(如,数据类型为int时,简单链表将会多出额外的两个指针)。
3、保持了快速插入和删除的性能。

压缩表(ziplist)

除了list,zset和hash,在元素个数较少的时候也会采用ziplist进行存储。
1、结构

struct ziplist<T> {
	int32 zlbytes;       //整个压缩表所占的字节数
	int32 zltail_offset; //最后一个元素距离压缩表起始位置的偏移量,用于快速定位到最后一个元素
	int16 zllength;      //元素个数
	T[] entries;		 //元素内容列表,依次紧凑存储
	int8 zlend;			 //结束符,值恒为0xFF
}

redis数据类型之list(列表)_第1张图片
其中entry结构如下:

struct entry {
	int<var> prevlen;		 //前一个entry的字节长度
	int<var> encoding;		 //元素类型编码
	optional byte[] content; //元素内容
}

redis数据类型之list(列表)_第2张图片
prevlen是一个边长的整数,当字符串长度小于254(0xFE)时,其长度为一个字节;当字符串长度大于等于254时,其长度为5个字节(第一个字节为0xFE,剩下四个字节表示字符串长度)。
encoding表示存储元素的编码类型信息。为了节省存储空间,encoding的设计相当复杂。一般是前几位表示存储类型及规模,比如长度小于63为的短字符串(00xxxxxx,其中xxxxxx为字符串的长度),中等长度的字符串(01xxxxxx,xxxxxxxx),极小整数(1111xxxx)等,然后后面的位就开始表示元素的值了,比如极小整数(1111xxxx)中的xxxx就开始表示整数且范围时(0001-1101),即1-13。而0000、1110、1111在其他编码中已经被占用了。而真实值都应该是value-1,实际范围为(0-12)。
content 其类型为optional,即可选的,对于极小数而言,其内容已经内联到coding字段尾部了。
2、增加元素
由于ziplist是紧凑存储的,没有冗余空间,所以当插入新元素时,就需要用realloc扩展内存,并将之前的内容一次性拷贝到新的地址(有可能在原有地址上进行扩展)。正是由于这个特性,ziplist的元素不易过大过多,否则内存会消耗很大。
3、级联更新
由于entry中的prevlen字段的大小可能为1字节也可能为5字节,当某个entry经过更新,其大小可能会从小于254变为大于等于254时,其下一个entry的prevlen将会从1个字节变为5个字节,如果后面每个entry长度都为253,那么后面所有entry的prevlen都会从1个字节变为5个字节,故这个操作是比较小号计算资源的。

快速列表

快速列表可以说是ziplist 和linkedlist的混合体。其将linkedlist按断切分,每一段使用ziplist让存储紧凑,多个ziplist间用双向指针串接起来,结构如下:
redis数据类型之list(列表)_第3张图片

struct ziplist {
	...
}
struct ziplist_compressed {
	int32 size;
	byte[] compressed_data;
}
struct quicklistNode {
	quicklistNode* prev;
	quicklistNode* next;
	ziplist* zl;            //指向压缩表
	int32 size;				//ziplist的字节总数	
	int16 count;			//ziplist中的元数数量
	int2 encoding;			//存储形式2bit,原生字节数组还是LZF压缩存储
	...
}
sturct quicklist {
	quicklistNode* head;
	quicklistNode* tail;
	long count;				//元素总数
	int nodes;				//ziplist节点个数
	int compressDepth;		//LZF算法压缩深度
}

其中ziplist结构可能会使用LZF算法进行压缩存储,这样可以进一步节约空间。

ziplist的长度
quicklist内部默认单个ziplist的长度为8KB,超过这个长度时,将会另起一个ziplist。如果需要自定义单个ziplist的长度,可以通过配置参数list-max-ziplist-size来决定。

压缩深度
quicklist默认的压缩深度为0,此时为不压缩。其配置参数为listcompress-depth。压缩深度为1时,quicklist的首尾两个ziplist不压缩,其他压缩。当压缩深度为2时,quicklist的首尾第一个和首尾第二个不压缩,其他的压缩。当压缩深度为1时,可以支持push/pop快速的进行操作。

你可能感兴趣的:(redis)