读书笔记-《Redis设计与实现》(一)数据结构与对象(下)

各位朋友新年快乐~

今天我们来继续学习 Redis 。


01

整数集合

当集合仅包含整数值,并且元素数量不多时,Redis 就会采用整数集合来作为集合键的底层实现。

cpp 复制代码
typedef struct intset {
    // 编码方式
    uint32_t encoding;
    
    // 元素数量
    uint32_t length;
    
    // 数组
    int8_t contents[];
} intset;

可以看到,contents 就是存储元素的地方,各个元素按从小到大排序并且不包含重复值。注意虽然 contents 声明为 int8_t,但实际类型取决于 encoding。

看到上面的数据结构,不难想到一个问题------如果现有的数组编码为 int16_t,然后往里面添加一个 int32_t 的元素,会发生什么?

答案就是升级。整数集合升级包含三个步骤:扩展数组空间、升级旧元素类型并重新放置、放置新元素。

升级的好处是:提升整体灵活性 (可以任意地往里面放置不同类型的元素)、尽可能节约内存


02

压缩列表

当列表键仅包含小整数值和短字符串,并且元素数量不多时,Redis 就会采用压缩列表来作为列表键的底层实现。

压缩列表的结构如下:

  • zlbytes:记录整个列表所占内存字节数。

  • zltail:记录表尾节点距离起始位置的偏移量。

  • zllen:记录节点数量,但最大值为 UINT16_MAX(65535),超过这个值就只能通过遍历才能得到具体数量。

  • entry:各个节点,结构包括 previous_entry_length、encoding、content。第一个属性顾名思义,记录前一个节点的长度,因此程序可通过当前节点的起始地址来计算出前一个节点的起始地址,进而实现从表尾向表头的遍历;后两个属性分别为节点值的编码和节点的值。

  • zlend:末端标识符 0xFF。


03

对象

cpp 复制代码
typedef struct redisObject {
    // 类型
    unsigned type:4;
    
    // 编码
    unsigned encoding:4;
    
    // 指向底层数据结构的指针
    void *ptr;
    
    // 引用计数,用于内存回收
    int refcount;
   
    // 记录最后一次被访问的时间,用于计算空转时长
    unsigned lru:22;
    
    // ...
    
} robj;

对象的关键属性如上。众所周知,Redis 包含五个对象类型,分别是字符串、列表、Hash、集合、有序集合。而我们在操作键值对时,键总是一个字符串对象,而值则可以是这五个中的任意一种。

编码则是我们前面学习的各种数据结构,例如 SDS、链表、字典、跳表、整数集合、压缩列表等等。通过这种切换编码的机制,极大提高了 Redis 的灵活性和效率

五种类型的编码如下:

  • 字符串对象: long 类型时编码为 int ,字符串且长度大于39字节时为 raw (即 SDS),字符串且长度小于等于39字节时为 embstr。embstr 的特点是使用连续内存空间存储 redisObject 和 sdshdr 两个结构,减少了内存分配和回收次数,提升了读取速度。

  • 列表对象: 所有字符串元素长度较小并且元素总数较少时(具体数值可配置)为 ziplist ,否则为 linkedlist

  • Hash 对象: 所有键值对的键和值字符串长度较小并且元素总数较少时(具体数值可配置)为 ziplist ,否则为 hashtable。在使用前者时,键值对会按照顺序存放,例如依次放入键值对"hello-world"、"happy-new year",则存储的节点顺序为 hello、world、happy、new year。

  • 集合对象: 所有元素为整数值且元素总数较少时(具体数值可配置)为 intset ,否则为 hashtable。在使用后者时,所有的键均没有值。

  • 有序集合对象: 所有元素长度较小且元素总数较少时(具体数值可配置)为 ziplist ,否则为 skiplist 。特殊的点在于,使用后者时对象的结构为 zset,其同时包含一个字典和一个跳跃表,从而使得该对象在单个元素查询和范围操作时,都有着极低的时间复杂度。

了解了对象的类型和编码后,我们不难得出,Redis 的命令(如 DEL、LLEN)是多态的。前者是基于类型的多态,一个命令同时处理不同类型的键;而后者是基于编码的多态,一个命令同时处理不同的编码。

另外,Redis 还会对只包含整数值的字符串对象进行共享,即多个键指向同一个地址以节省内存。共享的数值范围(如从 0 到 9999)同样可通过配置修改。这一点对于 Java 开发来说也不陌生了,出发点和 Integer 的缓存是一样的。


原文链接: 读书笔记-《Redis设计与实现》(一)数据结构与对象(下)

原创不易,点个关注不迷路哟,谢谢~

文章推荐:

相关推荐
charlie11451419125 分钟前
从0开始的操作系统手搓教程43——实现一个简单的shell
学习·操作系统·教程·shell·手搓教程
小呀小萝卜儿1 小时前
2025-03-07 学习记录--C/C++-PTA 习题8-6 删除字符
c语言·学习
WeiLai11121 小时前
面试基础--Redis 缓存穿透、缓存击穿、缓存雪崩深度解析
java·redis·分布式·后端·缓存·面试·架构
zctel1 小时前
java中小型公司面试预习资料(二):Redis
java·redis·面试
shyの同学1 小时前
分布式ID生成方案:数据库号段、Redis与第三方开源实现
redis·分布式·mysql·id
月落星还在1 小时前
Redis 的过期策略与键的过期时间设置
数据库·redis·bootstrap
爱吃烤鸡翅的酸菜鱼2 小时前
Java【网络原理】(3)网络编程续
java·运维·服务器·网络
虾球xz5 小时前
游戏引擎学习第147天
数据库·学习·游戏引擎
╰つ゛木槿6 小时前
Spring Boot 调用DeepSeek API的详细教程
java·spring boot·后端·deepseek
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧6 小时前
C语言_数据结构总结8:链式队列
c语言·开发语言·数据结构·链表·visualstudio·visual studio