Redis 5.0数据结构之压缩列表ziplist源码详解(二)

本文深入分析了Redis中ziplist的源码,包括添加元素、查找元素、删除元素的操作,以及连锁更新的实现。ziplist为节省内存,采用连续内存地址+偏移量方式存储,节点包含前驱节点长度。在修改操作中,ziplist可能触发连锁更新以保持prevlen正确。此外,文章还介绍了ziplist在Redis3.2之前的使用条件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由于篇幅过长,ziplist的源码分析分为两篇,本篇是第二篇,涉及到ziplist的添加元素、查找元素、删除元素以及连锁更新实现的源码分析。
Redis 5.0数据结构之压缩列表ziplist源码详解(一)

前文已经提及了压缩列表ziplist的主要设计初衷是尽量节约空间,因此设计出了一系列的压缩编码,将ziplist设计成了内存地址连续,使用基地址+偏移量的方式来访问压缩列表内节点,并且说明了压缩列表节点结构具有的属性以及相应的作用,通过源码分析了对于节点内各属性的编码的实现细节。

接下来将继续通过源码解析ziplist的各操作是如何实现的。

添加元素

添加元素是调用ziplistPush函数,其中参数where参数决定新元素插入位置。

unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {
   
    unsigned char *p;
    p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
    return __ziplistInsert(zl,p,s,slen);
}

实际完成添加元素工作的是__ziplistInsert函数,源码如下:

// 将值长度为slen的节点存放到zl压缩列表的位置p处
unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
   
	// 因为涉及到位运算 大小端转换
	// Redis默认小端存储 如果在大端存储的机器上允许 需要大小端转换 
	// reqlen为当前节点所需长度
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen;
    unsigned int prevlensize, prevlen = 0;
    // 偏移量
    size_t offset;
    int nextdiff = 0;
    unsigned char encoding = 0;
    long long value = 123456789; /* initialized to avoid warning. Using a value
                                    that is easy to see if for some reason
                                    we use it uninitialized. */
    zlentry tail;

    /* Find out prevlen for the entry that is inserted. */
    // 非尾部插入
    if (p[0] != ZIP_END) {
   
    	// 获取prevlen 宏定义函数见下
        ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
    // 尾部插入
    } else {
   
    	// 获取尾节点
        unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
        // 如果ziplist不为空
        if (ptail[0] != ZIP_END) {
   
        	// 则prevlen就是尾节点的长度
            prevlen = zipRawEntryLength(ptail);
        }
    }

    /* See if the entry can be encoded */
    // 尝试对value整数编码
    if (zipTryEncoding(s,slen,&value,&encoding)) {
   
        /* 'encoding' is set to the appropriate integer encoding */
        // 记录整数编码所需长度
        reqlen = zipIntSize(encoding);
    // 没法整数编码 则value保存的是字符串
    } else {
   
        /* 'encoding' is untouched, however zipStoreEntryEncoding will use the
         * string length to figure out how to encode it. */
        // 直接记录字节数组编码所需长度
        reqlen = slen;
    }
    /* We need space for both the length of the previous entry and
     * the length of the payload. */
    // 需要空间存放上一个节点的长度和当前节点长度
    // 分别获取并累加到reqlen中
    reqlen += zipStorePrevEntryLength(NULL,prevlen);
    reqlen += zipStoreEntryEncoding(NULL,encoding,slen);

    /* When the insert position is not equal to the tail, we need to
     * make sure that the next entry can hold this entry's length in
     * its prevlen field. */
    // forcelarge用于判断prevrawlen编码时需要使用哪个函数处理
    // forcelarge为0时 使用zipStorePrevEntryLength编码 该函数用于处理len小于254的情况
  	// forcelarge为1时 使用zipStorePrevEntryLengthLarge编码 该函数处理len大于等于254的情况  
    int forcelarge = 0;
    // 由于是插入操作 相当于新节点的下一个节点的前置节点更换了 需要判断下一个节点的prevlen是否够用
    // prevlen的值只有两种可能 分别是1或5 zipPrevLenByteDiff函数见下文注释
    nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
    // 此时有两种情况需要改变 即新节点len为1 下一个节点prevlen为5 或者相反
    // 判断成立的情况是 新节点len为1 下一个节点prevlen为5
    // 需要记录使用zipStorePrevEntryLengthLarge存储prevlen 并且记录nextdiff不需要扩容
    if (nextdiff == -4 && reqlen < 4) {
   
        nextdiff = 0;
        forcelarge = 1;
    }

    /* Store offset because a realloc may change the address of zl. */
    // 保存偏移量 因为realloc也可能改变ziplist的地址
    offset = p-zl;
    // 重设ziplist的大小
    zl = ziplistResize(zl,curlen+reqlen+nextdiff);
    p = zl+offset;

    /* Apply memory move when necessary and update tail offset. */
    // 如果不是在尾部插入
    if (p[0] != ZIP_END) {
   
        /* Subtract one because of the ZIP_END bytes */
        // 通过内存拷贝 使得原有数据后移
        memmove(p&#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

7rulyL1ar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值