Redis中数据结构的底层实现包括以下对象:
对象解释简单动态字符串 字符串的底层实现
链表 列表的底层实现
字典 运用在多个方面,包括Hash的实现等
跳跃表 有序集合的底层实现
整数集合 集合的底层实现之一
压缩字典 列表键和哈希键的底层实现之一
String
Redis中并没有直接使用C语言中的字符串,而是在其基础之上实现了字符串的数据结构,叫做简单动态字符串(SDS)。
其内部的定义为:
/* Redis简单动态字符串的数据结构 */ struct sdshdr { //字符长度,记录buf数组中已使用的字节数量 unsigned int len; //当前可用空间,记录buf数组中未使用的字节数量 unsigned int free; //具体存放字符的buf char buf[]; };
SDS和C字符串的区别
常数复杂度获取字符串长度
因为SDS纪录了自身字符串中已经使用的长度和未使用的长度,所以可以在O(1)的时间复杂度内获取到字符串长度,然而C字符串不得不通过遍历整个字符串才能获取到长度,其花费的则是O(N)。
杜绝缓冲区溢出
和C字符串不同的是,SDS会利用纪录下来的长度去检查自身是否还有足够的空间去容纳新的需求,如果不满足的话,会先进行扩容,然后才执行新的操作。
减少修改字符串时带来的内存重分配次数
C字符串中每次进行增加和缩短的操作时,都会涉及到内存的重新分配,SDS利用未使用空间来实现空间预分配和惰性空间释放这两种优化策略。
空间预分配
用于优化SDS的字符串增长操作:当要涉及到对SDS进行空间扩展的时候,程序不仅仅会为SDS分配修改所需要的空间,还会为SDS分配额外的未使用空间,好处在于在下次扩容的时候,如果未使用空间还足够使用的话,就使用未使用空间进行扩容。
惰性空间
用于优化SDS的字符串缩短操作:当要缩短SDS保存的字符串时,程序并不会立即使用内存重分配来回收缩短后多出来的字节,而是将其回收起来纪录到free空间中,以便将来继续使用。
二进制安全
C字符串中判断结束的条件是遇见空字符,不同的是,SDS则选择了通过自身的len属性的值来判断字符串是否结束,这样做的目的在于使得SDS不仅仅能够存储字符串,还能存储二进制。
兼容部分C字符串函数
通过遵循C字符串以空字符结尾的惯例,SDS可以在有需要的时候重用C语言中的string函数库,比如对比函数,追加函数等等,从而实现代码的重用。
链表链表提供了高效的结点重排能力,以及顺序性的结点访问方式,并且可以通过增删结点来灵活的调整链表的长度。
链表结点
每个链表结点使用的是一个listNode结构表示:
typedef struct listNode{ // 前置结点 struct listNode *prev; // 后置结点 struct listNode * next; // 结点值 void * value; }
链表在此基础之上,Redis通过封装了listNode实现双端链表,如下:
typedef struct list{ //表头节点 listNode * head; //表尾节点 listNode * tail; //链表长度 unsigned long len; //节点值复制函数 void *(*dup) (void *ptr); //节点值释放函数 void (*free) (void *ptr); //节点值对比函数 int (*match)(void *ptr, void *key); }