(转帖)大厂面试:Redis中的ziplist为什么会节省内存?能节省多少内存

本文深入探讨了Redis中ziplist的数据结构,它如何节省内存以及如何通过后向遍历提升性能。ziplist在list和hash中替代listNode及dictEntry,减少了内存消耗。例如,存储4字节字符串时,ziplist只需6字节,而listNode或dictEntry则需28字节。ziplist的属性如zlbytes、zltail等辅助高效访问,尤其适合热点元素的存储策略调整。

大家好,今天我们来聊一下在ziplist(压缩列表)中两个有趣的知识点。

先说两个结论:

  • ziplist使用紧凑的连续内存块顺序存储数据,在list或者hash结构中,未使用listNode(24字节)和dictEntry(24字节)结构体来存储元素项,因此会节省内存。
  • ziplist结构元素访问采用的是后向遍历(从后往前),因此在hash中可将热点的key或者在list中将热点的元素项放在最后,可以提升性能。

ziplist存储结构

| zlbytes | zltail | zllen | entry1 | entry2 | ... | entryN | zlend |

zlbytes:占4字节,记录整个压缩列表长度。

zltail:占4字节,记录压缩列表表尾节点距离压缩列表的起始地址有多少字节,通过偏移量,可以确定表尾节点的地址,用于后向遍历。

zllen:占2字节,记录了压缩列表包含的元素数量。

entryN:列表节点,长度由节点内容决定。

zlend:1字节,用于标记压缩列表的末端。

在以上ziplist的内存结构中,仅仅只使用了额外的11个字节来存储ziplist的属性,另外很重要的是ziplist使用后向遍历,当list或者hash中的元素较多时,可以根据元素的冷热性调整元素存储顺序。

listNode、dictEntry

Redis中,未使用ziplist存储数据时,list的元素项以listNode结构体存储,hash以dictEntry结构体存储,下面我们用几行代码来测试一下在64位机器上这两个结构体的内存占用情况:

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        int64_t u64;
        int64_t s64;
    } v;
    struct dictEntry *next;
} dictEntry;

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

int main(){
    printf("dictEntry size: %lu \n", sizeof(dictEntry));
    printf("listNode size: %lu \n", sizeof(listNode));
    return 0;
}

输出如下:

dictEntry size: 24 
listNode size: 24 

从输出来看,以上两个结构体都是占用24个字节的长度,在这24个字节中,元素的内容(上面结构体中的key、val)都是以指针声明形式存储在结构体中,也就是在结构体所占用的24字节中并未存储实际的元素内容。假设当你采用listNode来存储一个4字节的字符串时,你至少需要24+4=28字节的内存长度。而用ziplist的话,只需要6个字节就可以了

<think>嗯,用户想了解Redisziplist,作为Java程序员的面试问题。我需要先回忆一下ziplist的结构和用途。记得ziplistRedis为了节省内存设计的一种紧凑型数据结构,主要用于列表键和哈希键的底层实现,当元素数量少且体积小时使用。 首先,用户可能想知道ziplist是什么,为什么需要它。得解释清楚它的内存连续特性,没有指针开销,这样相比普通链表节省空间。然后结构部分,需要分头部、条目、尾部。每个条目包含前驱长度、当前长度、内容,可能还需要提到编码方式,比如整数和字符串的不同处理。 用户可能对具体例子感兴趣,比如如何存储一个包含三个元素的列表。这时候需要一步步拆解,比如插入"age"为12,然后"name"是"Tim","city"是"Beijing",展示每个条目如何编码,以及如何通过遍历找到元素。同时需要提到插入和删除时的内存移动,这可能影响性能,所以适合小数据量。 另外,面试中常问的问题可能包括ziplist的优缺点,使用场景,以及和普通链表的区别。需要强调内存效率和性能的权衡,比如查询效率可能不如哈希表,但内存占用低。可能还要提到配置参数,比如list-max-ziplist-size和hash-max-ziplist-entries,这些控制何时转换为其他结构。 用户作为Java程序员,可能对类似的数据结构感兴趣,可以对比Java中的ArrayList,虽然都是连续内存,但ziplist更紧凑,有变长编码。不过Java中没有完全对应的结构,这个类比可能帮助理解。 还要注意回答要通俗易懂,避免过多技术术语,用例子和类比来辅助说明。比如把ziplist比作火车车厢,每个车厢紧凑排列,没有空隙,这样节省空间。而普通链表像用绳子连接的车厢,有额外的绳子(指针)占用空间。 最后,确保覆盖应用场景,比如缓存小对象、临时数据存储,以及为什么在数据量大时切换结构。这样用户能全面理解ziplist的适用场景和限制。</think>我举个生活中例子帮你理解ziplist(压缩列表),就像火车站装货的集装箱列车: 🚂 [火车头][车厢1][车厢2][车厢3][车尾标记] Redisziplist就是这样一个结构: 1. 整个列表是连续的内存块(就像火车车厢紧密连接) 2. 每个元素保存前一个元素的长度(就像车厢连接处的卡扣) 3. 没有指针开销(不像普通链表需要存储前后指针) ▶ 实际存储示例: 假设我们存储一个哈希表:{"age":12, "name":"Tim", "city":"Beijing"} 内存结构大致如下: ``` [列表头][前驱长度][当前长度]12[前驱长度][当前长度]Tim[前驱长度][当前长度]Beijing[结束标记] ``` (实际存储会包含更多元数据,这里做了简化) ▶ 为什么说它内存? 普通链表存储一个int需要: - 8字节指针(64位系统) - 4字节存储int值 总共12字节 而ziplist存储12这个数字: - 前驱长度(1字节) - 当前长度(1字节) - 实际数据(用变长编码可能只需1字节) 总共3字节! ▶ 使用场景对比: | 场景 | 适合数据结构 | 原因 | |------------------|-------------|------------------------------| | 存储少量小数据 | ziplist | 内存利用率高,访问速度快 | | 存储大量/大数据 | hashtable | 避免频繁内存重组,查询效率更高 | ▶ Java程序员面试常见追问: 1. Q:ziplist如何实现快速遍历? A:通过保存前驱长度,可以像游标一样逐个元素计算位置(类似数组遍历) 2. Q:什么时候会从ziplist转成普通结构? A:当元素数量超过list-max-ziplist-entries(默认512)或单个元素超过list-max-ziplist-value(默认64字节) 3. Q:ziplist的缺点是什么? A:插入/删除需要内存重分配(类似数组的特性),大数据量时性能下降 💡 记忆技巧:把ziplist想象成用拉链把数据"压缩"在一起的链表,每个齿扣(元素)都知道前一个齿扣的位置。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值