Redis 第二章 Redis数据结构
3.1 简单动态字符串
Redis没有直接使用C语言传统的字符串表示,而是自己构建了名为**简单动态字符串(simple dynamic string ,SDS)**的抽象类型,并将SDS用作Redis的默认字符串表示。除了用来保存数据库中的字符串值之外,SDS还被用作缓冲区,例如AOF模块中的AOF缓冲区、客户端状态中的输入缓冲区等。
与c字符串的区别:
(1)常数复杂度获取字符串长度。
(2)杜绝缓冲区溢出。
(3)减少修改字符串时带来的内存重分配次数(空间预分配、惰性空间释放)
3.2 链表
链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点灵活地调整链表的长度。链表内置在许多高级的编程语言中,因为Redis使用的C语言并没有内置这种数据结构,所以Redis构建了自己的双向链表实现。当一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。除了链表键之外,发布与订阅、慢查询、监视器等功能也使用到了链表。
3.3 跳跃表
跳跃表是一种有序链表的数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。在大多数情况下,跳跃表的效率可以和平衡树(balance tree)相媲美,并且因为跳跃表的实现比平衡树更为简单,所以有不少程序使用跳跃表代替平衡树。Redis只在两个地方用到了跳跃表,一个是实现有序集合,另一个是集群节点中用作内部数据结构。
3.4 字典
字典,是一种用于保存键值对的抽象数据结构,C语言没有内置这种数据结构,因此Redis构建了自己的字典实现。字典在Redis中的应用相当广泛,比如Redis的数据库就是使用字典作为底层实现的,对数据库的增、删、改、查等操作也是构建在对字典的操作之上。
从下图结构可以看出,第一层是采用数组结构,第二层才采用table的结构,对于table的key是怎么进行hash的,请参考redis高级章节。
3.5 压缩列表
压缩列表是列表键和哈希键的底层实现,当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度较短的字符串,那么Redis就会使用压缩列表作为列表键的底层实现。
压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。
3.6 整数集合
整数集合是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。
contents数组是整数集合的底层实现:整数集合的每个元素都是contents数组的一个数组项,各个项在数组中从小到大有序排列,并且数组中不包含任何重复项。length属性记录了整数集合包含的元素数量,也即是contents数组的长度。
3.7 对象
Redis并没有直接使用前面所述的数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象五种类型的对象,每种对象都用到了至少一种前面介绍的数据结构。 Redis使用对象来表示数据库中的键和值,每次当我们在Redis数据库中新创建一个键值对时,至少会创建两个对象,一个对象用作键值对的键(键对象),另一个用作键值对的值(值对象)。
通过encoding属性来设定对象所使用的编码,而不是为特定的对象关联一种固定的编码,极大地提升了Redis的灵活性和效率,可以根据不同的场景设置不同的编码,从而优化对象在某一场景下的效率。例如,当列表对象包含元素比较少时,Redis使用压缩列表作为列表对象的底层实现。每种类型的对象都至少使用了两种不同的编码,如下表所示。
3.7.1 内存回收、对象共享
由于C语言并不具备自动内存回收功能,所以Redis在自己的对象系统中构建了一个引用计数(reference counting)技术实现内存回收机制,(类比JVM中的垃圾回收机制,因为Redis是单进程单线程方式,它的垃圾回收处理相对JVM简单很多),通过这一机制,程序可以跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。
Redis还通过引用计数技术实现对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享一个对象来节省内存。当数据库中保存的相同值对象越多,对象共享机制就能节约越多的内存。
(1)在创建一个新对象时,引用计数的值会被初始化为1;
(2)当对象被一个新程序使用时,它的引用计数值会被增一;
(3)当对象不再被一个程序使用时,它的引用计数值会被减一;
(4)当对象的引用计数值变为0时,对象所占用的内存会被释放;