42张图,带你真正搞懂redis数据类型的底层( 四 )


但是,我们并不是说不能释放SDS中空余的空间,SDS 提供了相应的API,让我们可以在 有需要 的时候,会 自行释放 SDS的空余空间;

通过惰性空间释放,SDS避免了缩短字符串时所需的 内存重分配 操作,并未将来可能有的增长操作提供了优化,嗯值得点赞!
 
二进制安全强调 的是C字符串中的字符必须符合某种 编码,并且除了字符串的末尾之外,字符串里面不包含 空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,那就尴尬了,这些 限制 使得C字符串只能保存文本数据,而不能保存想图片,音频,视频,压缩文件这样的 二进制 数据 。

但是在Redis中,不是靠空字符来判断字符串的结束的,而是通过 len 这个属性 。那么,即便是中间出现了空字符对于SDS来说,读取该字符仍然是可以的 。

如这样:

42张图,带你真正搞懂redis数据类型的底层

文章插图
 
兼容部分C字符串函数虽然SDS的API都是二进制安全的,但他们同样要遵循C字符串以 空字符串结尾 的惯例 。
再次总结 C字符串
SDS
获取字符串长度的复杂度为O(N)
获取字符串长度的复杂度为O(1)
API 是不安全的,可能会造成缓冲区溢出
API 是安全的,不会造成缓冲区溢出
修改字符串长度N次必然需要执行N次内存重分配
修改字符串长度N次最多执行N次内存重分配
只能保存文本数据
可以保存二进制数据和文本文数据
可以使用所有<String.h>库中的函数
可以使用一部分<string.h>库中的函数
压缩表压缩表是一种Redis用来 节省内存 的一系列特殊编码的 顺序性连续存储 的表结构,我们知道数组这种数据结构,每一个空间的大小都是一样的,这样我们存储较小元素节点的时候就会造成 内存的浪费,而压缩链表可以做到每一个元素的节点的 大小都不一样。当一个哈希键只含少量键值对,并且每个键值对的键和值也是小整数值或者长度比较短的字符串的时候,Redis就采用压缩列表做底层实现;
长这样:
42张图,带你真正搞懂redis数据类型的底层

文章插图
【42张图,带你真正搞懂redis数据类型的底层】 
图里 entry 的结构是这样的:
42张图,带你真正搞懂redis数据类型的底层

文章插图
 
previous_entry_length属性以字节为单位,记录了压缩列表中 前一个节点的长度。该属性的长度可以是1字节或者是5字节 。如果前一个节点的长度 小于
254 字节,那么该属性长度为1字节,保存的是小于 254的值 。那如果前一节点的长度
大于等于
254字节,那么长度需要为5字节,属性的第一字节会被设置为0xFE (254)之后的4个字节保存其长度 。
参数:zlbytes :4 字节 。记录整个压缩列表占用的内存字节数,在内存重分配或者计算 zlend 的位置时使用 。
zltail :4 字节 。记录压缩列表表尾节点记录压缩列表的起始地址有多少个字节,可以通过该属性直接确定表尾节点的地址,无需遍历 。
zllen :2 字节 。记录了压缩列表包含的节点数量,由于只有2字节大小,那么小于65535时,表示节点数量 。等于 65535 时,需要遍历得到总数 。
entry :列表节点,长度不定,由内容决定 。
zlend
:1字节,特殊值 0xFF,来标记压缩列表的结束 。
压缩列表节点保存的是 一个字节数组 或者 一个整数值 :字节数组可以是下列值:
  • 长度小于等于 2^6-1 字节的字节数组
  • 长度小于等于 2^14-1 字节的字节数组
  • 长度小于等于 2^32-1 字节的字节数组整数可以是六种长度;
  • 4 位长,介于 0 到 12 之间的无符号整数
  • 1 字节长的有符号整数
  • 3 字节长的有符号整数
  • int16_t 类型整数
  • int32_t 类型整数
  • int64_t 类型整数
元素的遍历先找到列表尾部元素:
42张图,带你真正搞懂redis数据类型的底层

文章插图
 
然后再根据 ziplist 节点元素中的 previous_entry_length 属性,来逐个来遍历:
42张图,带你真正搞懂redis数据类型的底层

文章插图
 
连锁更新再次看看 entry元素的结构,有一个 previous_entry_length 字段,它的长度要么都是1个字节,要么都是5个字节:


推荐阅读