压缩列表(Ziplist)是Redis中一种紧凑型的数据结构,用于存储小量的字符串或整数。它的设计初衷是为了节省内存,尤其适用于存储短小数据的场景,如列表或哈希表中的少量元素。Ziplist通过连续的内存块存储数据,并使用一种紧凑的编码方式来表示字符串和整数,从而减少内存占用。
主要结构
Ziplist的结构可以分为三部分:表头、数据项和表尾。
- 表头 :包含两个字段:
zlbytes和zltail。zlbytes:整个压缩列表所占的字节数。zltail:到压缩列表尾部的偏移量。zllen:压缩列表包含的节点数。
- 数据项:每个数据项都有三部分:前置节点长度、编码方式和实际数据。
- 表尾 :用一个字节
0xFF标识压缩列表的结束。
压缩列表的完整结构
c
/*
* |<zlbytes>|<zltail>|<zllen>|<entry>...|<entry>|<zlend>|
*/
typedef struct ziplist {
uint32_t zlbytes; // 压缩列表所占用的总字节数
uint32_t zltail; // 到压缩列表尾部的偏移量
uint16_t zllen; // 压缩列表包含的节点数
unsigned char entries[]; // 压缩列表的实际数据部分
} ziplist;
数据项的结构
每个数据项由以下部分组成:
- 前置节点长度(PrevEntryLength):表示前一个节点的长度。
- 编码方式(Encoding):表示当前节点的类型和编码方式。
- 实际数据(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)通过紧凑的内存布局和灵活的编码方式实现了高效的内存利用。它适用于存储短小数据的场景,通过精心设计的数据结构,保证了高效的插入、删除和查找操作。