【面试】Redis底层数据结构——ZipList压缩列表

ZipList压缩列表

ziplist是一个经过特殊编码的双向链表,它的设计目标就是为了提高存储效率。ziplist可以用于存储字符串或整数,其中整数是按真正的二进制表示进行编码的,而不是编码成字符串序列。它能以O(1)的时间复杂度在表的两端提供push和pop操作。

数据结构

【面试】Redis底层数据结构——ZipList压缩列表_第1张图片

属性 说明
zlbytes 记录整个压缩列表占用的内存字节数;在对压缩列表进行内存重分配,或者计算zlend的位置时使用
zltail 记录压缩列表表尾节点距离头结点有多少字节,无须遍历就能够确定表尾节点。
zllen 记录压缩列表的节点数量。
entry 压缩列表的节点
zlend 标记压缩列表末端
previous_entry_length 记录压缩列表中前一个节点的长度,1或者5字节;可以通过指针运算计算出前一个节点的起始位置
encoding 记录content属性所保存数据的类型以及长度
content 保存节点的值,值得类型和长度由节点的encoding属性决定

3.2之前

/* Create a new empty ziplist. */
unsigned char *ziplistNew(void) {
	// 表头加末端大小
    unsigned int bytes = ZIPLIST_HEADER_SIZE+1;
    // 为表头和表末端分配空间
    unsigned char *zl = zmalloc(bytes);
    // 初始化表属性
    ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
    ZIPLIST_LENGTH(zl) = 0;
    // 设置表末端
    zl[bytes-1] = ZIP_END;
    return zl;
}
typedef struct zlentry {
    // 前一节点长度信息的长度
    unsigned int prevrawlensize;
    // 前一节点长度
    unsigned int prevrawlen;
    // 当前节点长度信息长度
    unsigned int lensize;  
    // 当前节点长度
    unsigned int len;
    // 当前节点头部信息长度
    unsigned int headersize;
    // 当前节点数据编码
    unsigned char encoding;     
    unsigned char *p;           
} zlentry;

void zipEntry(unsigned char *p, zlentry *e) {
    // 前一节点长度信息解析
    ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
    // 当前节点数据长度与编码信息解析
    ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
    e->headersize = e->prevrawlensize + e->lensize;
    e->p = p;
}

问题

连锁更新

在这里插入图片描述
当在entry1之前添加一个节点,此时如果entry1previous_entry_length 只记录 1 个长度,然而新节点如果大于254字节那么新节点的长度要用5字节长的空间来保存。entry1所占的空间就需要往后移动,重新分配空间。扩展entry1也可能导致需要扩展entry2…相互影响导致连锁更新
但是这种情况不常见,一半不影响性能

重点

  1. 压缩列表是一种为节约内存而开发的顺序性数据结构。
  2. 压缩列表被用作列表键和哈希键的底层实现之一。
    每当有新的键值对要加入到哈希对象时, 程序会先将保存了键的压缩列表节点推入到压缩列表表尾, 然后再将保存了值的压缩列表节点推入到压缩列表表尾。查找并非 O ( 1 ) O(1) O(1)
  3. 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
  4. 新增或者删除节点可能导致连锁更新。

你可能感兴趣的:(#,redis,面试)