数据结构-ziplist压缩列表

压缩列表实际上类似于一个数组,数组中的每一个元素都对应保存一个数据。

 压缩列表结构

struct ziplist<T> {
    int32 zlbytes;
    int32 zltail_offset;
    int16 zllength;
    T[] entries;
    int8 zlend;
}
  • zlbytes:整个压缩列表占用的字节数,占4Byte。
  • zltail_offset:最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个元素,占4Byte。
  • zllength:压缩列表的元素个数,占2Byte。
  • entries:压缩列表的元素,可以包含多个节点,每个节点可以保存一个字节数组或者一个整数值。
  • zlend:压缩列表结束标志,值等于 0xFF,占1Byte。

压缩列表节点结构

typedef struct zlentry { 
    unsigned int prevrawlensize, prevrawlen;
    unsigned int lensize, len;
    unsigned int headersize;
    unsigned char encoding;
    unsigned char *p;
} zlentry;
  • prevrawlen:前一个节点的长度
  • prevrawlensize:存储前一个节点长度(prevrawlen属性)所需的字节数
  • len:当前节点长度
  • lensize:储当前节点长度(len属性)所需的字节数
  • headersize:当前节点的header大小
  • encoding:节点的编码方式
  • p:指向节点的指针

虽然redis定义了节点zlentry结构体,但是redis却没有用zlentry结构来存储节点,因为,这个结构存小整数或短字符串太浪费空间

zlentry结构体在32位系统占用28Byte,在64位系统占用32Byte,这不符合压缩列表提高内存利用率的设计目的,因此,在redis中,并没有使用zlentry结构,而是定义了宏来表示压缩列表的节点。

压缩列表的节点真正的结构如下图所示:

  • previous_entry_length:前一个节点的长度,占1Byte或5Byte。
    • 如果前一个节点的长度小于254Byte,则需要1Byte来保存前一个节点的长度。
    • 如果前一个节点的长度大于等于254Byte,则需要5Byte来保存前一个节点的长度,第一个Byte固定为0xfe(254),后四个Byte表示前一个节点的长度。
  • encoding:编码类型(字节数组,整数),保存了content的数据类型和长度,占用1Byte、2Byte或者5Byte。
  • content:节点数据,节点数据类型和长度由encoding决定。
当前entry的总字节数 = previous_entry_length字节数 + encoding字节数 + content字节数=下一个entry的previous_entry_length的值。

压缩列表查找

0
在压缩列表中,如果我们要查找定位第一个元素和最后一个元素,可以通过表头三个字段的长度直接定位,复杂度是 O(1)。而查找其他元素时,就没有这么高效了,只能逐个查找,此时的复杂度就是 O(N) 了。
它的好处是更能节省内存空间,因为它所存储的内容都是在连续的内存区域当中的。当列表对象元素不大,每个元素也不大的时候,就采用ziplist存储。但当数据量过大时就ziplist就不是那么好用了。因为为了保证他存储内容在内存中的连续性,插入的复杂度是O(N),即每次插入都会进行realloc重新分配内存。
 
posted on 2023-03-25 16:28  zhengbiyu  阅读(32)  评论(0编辑  收藏  举报