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)通过紧凑的内存布局和灵活的编码方式实现了高效的内存利用。它适用于存储短小数据的场景,通过精心设计的数据结构,保证了高效的插入、删除和查找操作。

相关推荐
q***82912 小时前
Spring Boot 热部署
java·spring boot·后端
Victor3562 小时前
Redis(131)Redis的整数集合(Intset)是如何实现的?
后端
yuuki2332333 小时前
【数据结构】栈
c语言·数据结构·后端
程序猿小蒜6 小时前
基于springboot的共享汽车管理系统开发与设计
java·开发语言·spring boot·后端·spring·汽车
q***46528 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端
hygge9998 小时前
Spring Boot + MyBatis 整合与 MyBatis 原理全解析
java·开发语言·经验分享·spring boot·后端·mybatis
q***13618 小时前
十七:Spring Boot依赖 (2)-- spring-boot-starter-web 依赖详解
前端·spring boot·后端
q***25218 小时前
Spring Boot接收参数的19种方式
java·spring boot·后端
WX-bisheyuange8 小时前
基于Spring Boot的民谣网站的设计与实现
java·spring boot·后端