Redis(130)Redis的压缩列表(Ziplist)是如何实现的?

压缩列表(Ziplist)是Redis中一种紧凑型的数据结构,用于存储小量的字符串或整数。它的设计初衷是为了节省内存,尤其适用于存储短小数据的场景,如列表或哈希表中的少量元素。Ziplist通过连续的内存块存储数据,并使用一种紧凑的编码方式来表示字符串和整数,从而减少内存占用。

主要结构

Ziplist的结构可以分为三部分:表头、数据项和表尾。

  1. 表头 :包含两个字段:zlbyteszltail
    • zlbytes:整个压缩列表所占的字节数。
    • zltail:到压缩列表尾部的偏移量。
    • zllen:压缩列表包含的节点数。
  2. 数据项:每个数据项都有三部分:前置节点长度、编码方式和实际数据。
  3. 表尾 :用一个字节0xFF标识压缩列表的结束。

压缩列表的完整结构

c 复制代码
/*  
 * |<zlbytes>|<zltail>|<zllen>|<entry>...|<entry>|<zlend>|
 */
typedef struct ziplist {
    uint32_t zlbytes;  // 压缩列表所占用的总字节数
    uint32_t zltail;   // 到压缩列表尾部的偏移量
    uint16_t zllen;    // 压缩列表包含的节点数
    unsigned char entries[]; // 压缩列表的实际数据部分
} ziplist;

数据项的结构

每个数据项由以下部分组成:

  1. 前置节点长度(PrevEntryLength):表示前一个节点的长度。
  2. 编码方式(Encoding):表示当前节点的类型和编码方式。
  3. 实际数据(Content):当前节点的实际数据。

数据项的结构定义

c 复制代码
typedef struct ziplistEntry {
    unsigned int prevrawlen;  // 前一个节点的长度
    unsigned int encoding;    // 当前节点的数据类型和编码方式
    unsigned char *p;         // 指向实际数据的指针
} ziplistEntry;

主要操作

1. 压缩列表的创建

创建一个新的压缩列表,并初始化其头部和尾部。

c 复制代码
unsigned char *ziplistNew(void) {
    unsigned int bytes = ZIPLIST_HEADER_SIZE+1; // 头部 + 尾部
    unsigned char *zl = zmalloc(bytes);

    ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);  // 设置 zlbytes
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);  // 设置 zltail
    ZIPLIST_LENGTH(zl) = 0;  // 设置 zllen
    zl[bytes-1] = ZIP_END;  // 设置 zlend

    return zl;
}

2. 插入操作

插入一个新的数据项到压缩列表中。

c 复制代码
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl));
    size_t reqlen;
    unsigned char encoding = 0;
    long long value;

    // 计算插入数据所需的空间
    if (zipTryEncoding(s, slen, &value, &encoding)) {
        reqlen = zipIntSize(encoding);
    } else {
        reqlen = slen;
    }

    // 计算新节点的总长度
    reqlen += zipStorePrevEntryLength(NULL, curlen);
    reqlen += zipStoreEntryEncoding(NULL, encoding, slen);

    zl = zrealloc(zl, curlen + reqlen);
    p = zl + (p - zl);

    // 更新头部信息
    memmove(p + reqlen, p, curlen - (p - zl));
    zipStorePrevEntryLength(p, curlen);
    p += zipStoreEntryEncoding(p, encoding, slen);
    
    if (encoding == ZIP_INT_8B) {
        p[0] = (unsigned char)value;
    } else if (encoding == ZIP_INT_16B) {
        int16_t i16 = (int16_t)value;
        memcpy(p, &i16, sizeof(i16));
    } else if (encoding == ZIP_INT_32B) {
        int32_t i32 = (int32_t)value;
        memcpy(p, &i32, sizeof(i32));
    } else if (encoding == ZIP_INT_64B) {
        int64_t i64 = (int64_t)value;
        memcpy(p, &i64, sizeof(i64));
    } else {
        memcpy(p, s, slen);
    }

    ZIPLIST_BYTES(zl) = intrev32ifbe(curlen + reqlen);
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(curlen + reqlen - (p - zl));
    ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl)) + 1);

    return zl;
}

3. 删除操作

从压缩列表中删除一个数据项。

c 复制代码
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p) {
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl));
    size_t reqlen = zipRawEntryLength(*p);
    unsigned char *ptr = *p;
    size_t offset = ptr - zl;

    // 删除节点
    memmove(ptr, ptr+reqlen, curlen-(offset+reqlen));
    zl = zrealloc(zl, curlen-reqlen);

    // 更新头部信息
    ZIPLIST_BYTES(zl) = intrev32ifbe(curlen-reqlen);
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-reqlen);
    ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))-1);

    return zl;
}

辅助函数

编码和存储长度

c 复制代码
unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {
    if (p == NULL) {
        return (len < ZIP_BIG_PREVLEN) ? 1 : 5;
    } else {
        if (len < ZIP_BIG_PREVLEN) {
            p[0] = len;
            return 1;
        } else {
            p[0] = ZIP_BIG_PREVLEN;
            memcpy(p+1, &len, sizeof(len));
            return 5;
        }
    }
}

unsigned int zipStoreEntryEncoding(unsigned char *p, unsigned char encoding, unsigned int rawlen) {
    if (p == NULL) {
        if (ZIP_IS_STR(encoding)) {
            if (rawlen <= 0x3f) {
                return 1;
            } else if (rawlen <= 0x3fff) {
                return 2;
            } else {
                return 5;
            }
        } else {
            return 1;
        }
    } else {
        if (ZIP_IS_STR(encoding)) {
            if (rawlen <= 0x3f) {
                p[0] = ZIP_STR_06B | rawlen;
                return 1;
            } else if (rawlen <= 0x3fff) {
                p[0] = ZIP_STR_14B | ((rawlen >> 8) & 0x3f);
                p[1] = rawlen & 0xff;
                return 2;
            } else {
                p[0] = ZIP_STR_32B;
                memcpy(p+1, &rawlen, sizeof(rawlen));
                return 5;
            }
        } else {
            p[0] = encoding;
            return 1;
        }
    }
}

示例使用

c 复制代码
int main() {
    unsigned char *zl = ziplistNew();
    unsigned char *p;

    // 插入数据项
    zl = ziplistInsert(zl, zl + ZIPLIST_HEADER_SIZE, (unsigned char *)"hello", 5);
    zl = ziplistInsert(zl, zl + ZIPLIST_HEADER_SIZE, (unsigned char *)"world", 5);

    p = zl + ZIPLIST_HEADER_SIZE;
    while (*p != ZIP_END) {
        // 访问数据项
        printf("%s\n", p + 1); // 打印数据项内容
        p += zipRawEntryLength(p);
    }

    // 删除数据项
    zl = ziplistDelete(zl, &p);

    return 0;
}

总结:压缩列表(ziplist)通过紧凑的内存布局和灵活的编码方式实现了高效的内存利用。它适用于存储短小数据的场景,通过精心设计的数据结构,保证了高效的插入、删除和查找操作。

相关推荐
神奇的程序员1 小时前
从已损坏的备份中拯救数据
运维·后端·前端工程化
oden1 小时前
AI服务商切换太麻烦?一个AI Gateway搞定监控、缓存和故障转移(成本降40%)
后端·openai·api
李慕婉学姐2 小时前
【开题答辩过程】以《基于Android的出租车运行监测系统设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·后端·vue
m0_740043732 小时前
SpringBoot05-配置文件-热加载/日志框架slf4j/接口文档工具Swagger/Knife4j
java·spring boot·后端·log4j
招风的黑耳4 小时前
我用SpringBoot撸了一个智慧水务监控平台
java·spring boot·后端
Miss_Chenzr4 小时前
Springboot优卖电商系统s7zmj(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
期待のcode4 小时前
Springboot核心构建插件
java·spring boot·后端
2501_921649494 小时前
如何获取美股实时行情:Python 量化交易指南
开发语言·后端·python·websocket·金融
serendipity_hky4 小时前
【SpringCloud | 第5篇】Seata分布式事务
分布式·后端·spring·spring cloud·seata·openfeign
五阿哥永琪5 小时前
Spring Boot 中自定义线程池的正确使用姿势:定义、注入与最佳实践
spring boot·后端·python