数据库|15张图解Redis为什么这么快( 二 )



  • 惰性空间释放
当然 , 有空间分配对应的就有空间释放 。
SDS 缩短时 , 并不会回收多余的内存空间 , 而是使用 free 字段将多出来的空间记录下来 。 如果后续有变更操作 , 直接使用 free 中记录的空间 , 减少了内存的分配 。
(3)二进制安全
【数据库|15张图解Redis为什么这么快】你已经知道了 Redis 可以存储各种数据类型 , 那么二进制数据肯定也不例外 。 但二进制数据并不是规则的字符串格式 , 可能会包含一些特殊的字符 , 比如 '\\0' 等 。
前面我们提到过 , C 中字符串遇到 '\\0' 会结束 , 那 '\\0' 之后的数据就读取不上了 。 但在 SDS 中 , 是根据 len 长度来判断字符串结束的 。
看 , 二进制安全的问题就解决了 。
2、双端链表
列表 List 更多是被当作队列或栈来使用的 。 队列和栈的特性一个先进先出 , 一个先进后出 。 双端链表很好的支持了这些特性 。

- 双端链表 -
(1)前后节点
链表里每个节点都带有两个指针 , prev 指向前节点 , next 指向后节点 。 这样在时间复杂度为 O(1) 内就能获取到前后节点 。
(2)头尾节点
你可能注意到了 , 头节点里有 head 和 tail 两个参数 , 分别指向头节点和尾节点 。 这样的设计能够对双端节点的处理时间复杂度降至 O(1)  , 对于队列和栈来说再适合不过 。 同时链表迭代时从两端都可以进行 。
(3)链表长度
头节点里同时还有一个参数 len , 和上边提到的 SDS 里类似 , 这里是用来记录链表长度的 。 因此获取链表长度时不用再遍历整个链表 , 直接拿到 len 值就可以了 , 这个时间复杂度是 O(1) 。
你看 , 这些特性都降低了 List 使用时的时间开销 。
3、压缩列表
双端链表我们已经熟悉了 。 不知道你有没有注意到一个问题:如果在一个链表节点中存储一个小数据 , 比如一个字节 。 那么对应的就要保存头节点 , 前后指针等额外的数据 。
这样就浪费了空间 , 同时由于反复申请与释放也容易导致内存碎片化 。 这样内存的使用效率就太低了 。
于是 , 压缩列表上场了!

它是经过特殊编码 , 专门为了提升内存使用效率设计的 。 所有的操作都是通过指针与解码出来的偏移量进行的 。
并且压缩列表的内存是连续分配的 , 遍历的速度很快 。
4、字典

Redis 作为 K-V 型数据库 , 所有的键值都是用字典来存储的 。

日常学习中使用的字典你应该不会陌生 , 想查找某个词通过某个字就可以直接定位到 , 速度非常快 。 这里所说的字典原理上是一样的 , 通过某个 key 可以直接获取到对应的value 。

字典又称为哈希表 , 这点没什么可说的 。 哈希表的特性大家都很清楚 , 能够在 O(1) 时间复杂度内取出和插入关联的值 。

5、跳跃表
作为 Redis 中特有的数据结构-跳跃表 , 其在链表的基础上增加了多级索引来提升查找效率 。

这是跳跃表的简单原理图 , 每一层都有一条有序的链表 , 最底层的链表包含了所有的元素 。 这样跳跃表就可以支持在 O(logN) 的时间复杂度里查找到对应的节点 。

下面这张是跳表真实的存储结构 , 和其它数据结构一样 , 都在头节点里记录了相应的信息 , 减少了一些不必要的系统开销 。
合理的数据编码

对于每一种数据类型来说 , 底层的支持可能是多种数据结构 , 什么时候使用哪种数据结构 , 这就涉及到了编码转化的问题 。

那我们就来看看 , 不同的数据类型是如何进行编码转化的: