一、简介

redis有五种基本数据结构,每种基本数据结构都有两种以上的内部编码实现

二、内部编码:

二、String字符串

1.简介

字符串对象是Redis内部最常用的数据类型。所有的键都是字符串类 型,值对象数据除了整数之外都使用字符串存储Redis没有采用原生C语言的字符串类型而是自己实现了字符串结构,内 部简单动态字符串(simple dynamic string,SDS)

String类型的底层用的什么结构 redis redis的string底层数据结构_Redis

 

2.该结构的优点

(1)O(1)时间复杂度获取:字符串长度、已用长度、未用长度

(2)可用于保存字节数组,支持安全的二进制数据存储(bitmap)。

(3)内部实现空间预分配机制,降低内存再分配次数

(4)惰性删除机制,字符串缩减后的空间不释放,作为预分配空间保留。

 

3注意事项:

字符串追加操作后字符串对象预分配了一倍容量作为预留空间,所以尽量减少字符串频繁修改操作如append、setrange,改为直接使用set修改字符串,降低预分配带来的内存浪费和内存碎片化

4其他应用:

SDS除了用来保存数据库中的字符串之外,SDS还被用作缓冲区(buffer),如AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区

二、链表

链表结构是双向非循环链表,其实现与Java中LinkedList相同,略过

String类型的底层用的什么结构 redis redis的string底层数据结构_Redis_02

使用场景

当Redis中使用链表结构,且链表中的元素个数较多或者链表中保存字符串长度较长时,Redis中的链表会使用LinkedList来实现。

三、Hash表

1.简介:

字典在高级语言中是一种很普遍的数据结构,redis在Hash实现与Java中的HashMap实现相似

2.哈希冲突例子

既然是哈希表,那么就会存在哈希键冲突的场景,解决哈希冲突有好几种方法,这里是采用链地址法(但redis中并没有引入红黑树优化),next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接起来在一起

String类型的底层用的什么结构 redis redis的string底层数据结构_字符串_03

3.dict的rehash

当哈希表的大小不能满足需求,就可能会有两个或者以上数量的键被分配到了哈希表数组上的同一个索引上,于是就发生冲突(collision),在Redis中解决冲突的办法就是我上面提到是拉链法(separate chaining)。

但是我们仍然需要尽可能避免冲突,希望哈希表的负载因子(load factor),维持在一个合理的范围之内,就需要对哈希表进行扩展或收缩。

Rehsh会根据负载因子(load_factor = ht[0].used/ht[0].size)调整,当满足如下任意条件时,哈希表会rehash拓展:

  1. 在服务器没有执行BGSAVE或BGREWRITEAOF,即没有持久化数据的时候,如果负载因子大于等于1
  2. 在服务器正在执行BGSAVE或BGREWRITEAOF时,如果负载因子大于等于5

Rehash扩展有三个步骤:

  1. 扩展备用的ht[1],将它的容量扩张到第一个大于ht[0].used*2的 2的n次方
  2. 将ht[0]的值重新经过hash索引之后迁移到ht[1]上。
  3. 释放ht[0],将ht[1]设为ht[0],创建新的空表ht[1]。(当负载因子小于0.1时,进行收缩操作,大于变为小于

4.总结:

(1)字典被广泛用于实现 Redis 的各种功能, 其中包括数据库和哈希键。
(2)Redis 中的字典使用哈希表作为底层实现, 每个字典带有两个哈希表, 一个用于平时使用, 另一个仅在进行 rehash 时使用。
(3)当字典被用作数据库的底层实现, 或者哈希键的底层实现时, Redis 使用 MurmurHash2 算法来计算键的哈希值。
(4)哈希表使用链地址法来解决键冲突, 被分配到同一个索引上的多个键值对会连接成一个单向链表。
(5)在对哈希表进行扩展或者收缩操作时, 程序需要将现有哈希表包含的所有键值对 rehash 到新哈希表里面, 并且这个 rehash 过程并不是一次性地完成的, 而是渐进式地完成的

5.使用场景:

Hash结构主要用于Redis的三种类型中

(1)当字典结构中的键值对数量较多,或者键对象与值对象的长度较长时,Redis字典实现修改为Hash

(2)当集合元素中元素个数较多,或者元素保存内容长度较长时,Redis集合的实现修改为Hash(key->null)

(3)当有序集合元素较多,或者集合中元素保存内容较长时,Redis有序集合会使用Hash保存集合元素到分值的映射(仅仅是为了更快根据集合元素获取元素得分,在有序集合实现中,Hash与SkipList一块使用的

四、跳跃表 skiplist

1.简介

跳跃表是有序集合zset的底层实现之一(另一个是压缩列表),当元素数量比较多,或者元素成员是比较长的字符串时,底层实现采用跳跃表。

跳跃表是一种有序数据结构,他在一个节点中维持多个指向其他节点的指针

跳跃表的平均复杂度为O(logN),最坏为O(N),其效率可以和平衡树相媲美,而且跟平衡树相比,实现简单

2.跳跃表的查询

跳跃表的查询是从顶层往下找,那么会先从第顶层开始找,方式就是循环比较,如过顶层节点的下一个节点为空说明到达末尾,会跳到第二层,继续遍历,直到找到对应节点。

String类型的底层用的什么结构 redis redis的string底层数据结构_Redis_04

例子:查找元素 117

  1. 比较 21, 比 21 大,且21有后继,向后面找
  2. 比较 37, 比 37大,且37节点同层没有后继了,则从 37 的下面一层开始找
  3. 比较 71, 比 71 大,且71节点同层没有后继了,则从 71 的下面一层开始找
  4. 比较 85, 比 85 大,且85有后继,向后面找
  5. 比较 117, 等于 117, 找到了节点。

五、整数集合 intset

1.简介

整数集合是set的底层实现之一,当一个集合中只包含整数值,并且元素数量不多时,redis使用整数集合作为set的底层实现。整数集合可以保存int16、 int32、 int_64类型的整数。

2.参数

String类型的底层用的什么结构 redis redis的string底层数据结构_Redis_05

  • Encoding 存储编码方式
  • Length inset的长度,即元素数量
  • Content Int数组,用来保存元素,各个项在数组中按数值从小到大排序,不包含重复项

3.升级操作(不存在降级操作)

当往编码为int16的整数集合中添加一个int32的整数时,整数集合就会进行扩容操作,将int16类型的整数全部升级为int32表示,然后插入int32的整数。这样可以保证在一个数组中保存的时同一类型的数据。数据都存放在一个int8类型的数组中。(但是不会出现int_8类型的整数)

4.升级的好处

(1)提高灵活性。C 语言是静态类型语言, 为了避免类型错误, 我们通常不会将两种不同类型的值放在同一个数据结构里面。比如说, 我们一般只使用 int16_t 类型的数组来保存 int16_t 类型的值, 只使用 int32_t 类型的数组来保存 int32_t 类型的值, 诸如此类。但是, 因为整数集合可以通过自动升级底层数组来适应新元素, 所以我们可以随意地将 int16_t 、 int32_t 或者 int64_t 类型的整数添加到集合中, 而不必担心出现类型错误, 这种做法非常灵活
(2)节约内存。

5.使用场景

当Redis的集合元素中元素全部为数字并且元素个数较少时,Redis集合的实现使用的就是整数集合

六、压缩列表

1.简介:

压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。压缩列表也是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点(entry),一个节点可以保存一个字节数组或一个整数值。

String类型的底层用的什么结构 redis redis的string底层数据结构_Redis_06

 

  • Zlbytes 类型:uint32_t 记录整个压缩表占用的内存字节数,对压缩表进行内存重分配和或者计算zlend位置时被使用
  • Zltail_offset 类型:uint32_t 记录压缩列表尾节点entryN距离压缩列表的起始地址的字节数。用来快速确定表尾节点的地址。
  • Zllength 类型:uint16_t 若不超过uint16的极值65535,就是记录着压缩表节点的数量。否则,真实的节点数量需要遍历压缩表才能得出
  • Zlend 类型:uint8_t 特殊值0xFF(十进制255),用于标记表的末端。
  • Entry char[]或uint 长度不定,节点的长度随保存的内容而改变。

2.特点:

  • 压缩列表是一种为了节约内存而开发的顺序型数据结构。压缩列表被用作列表键和哈希键的底层实现之一
  • 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值

3.应用场景:

压缩列表用于Redis的3种类型中:

(1)当Redis 列表元素个数非常少并且元素长度较短时,Redis列表底层实现使用ZipList

(2)当Redis字典中元素个数较少且元素长度较短时,Redis字典底层实现使用ZipList

(3)当Redis有序集合中元素个数较少时且元素长度较短时,Redis有序集合底层实现使用ZipList