Redis数据结构(1)

569 阅读4分钟

Redis数据结构-String&LinkTable

简单动态字符串

redis底层是C语言,但是没有直接使用C语言的传统的字符串表示(以空字符结尾的字符数组),而是自己构建了一种名为简单动态字符串SDS(simple dynamic string),在redis里面,C字符串只是用在一些无须对字符串值进行修改的地方。

SDS的定义

struct sdshdr{ 
    // 记录buf数组中已使用的字节的数量=SDS所保存的字符串的长度 
    int len; 
    // 记录buf数组中未使用字节的数量 
    int free; 
    // 字节数组,用于保存字符串 
    char buf[]; }

buf 仍然是以C语言字符串为准,空字符串结束
len 的长度也不计算空字符串那位。

空字符串的存在对调用SDS函数的人完全透明。

Q:为什么buf仍然以C语言为准?
A:可以直接重用一部分C字符串函数库里面的函数。例如:printf("%s", s->buf);

SDS与C字符串的区别

  • 常数复杂度获取字符串长度

C:遍历整个字符串
SDS:get len ,确保获取字符串长度的工作不会成为redis的瓶颈(strlen命令)

  • 杜绝缓冲区溢出

C:在进行字符产strcat时,默认用户已经分配了足够的缓存,直接相加,会造成缓冲区溢出
SDS:空间分配策略(先判断空间是否满足修改要求),不满足时,先扩展,后执行修改。例如sdscat函数。

  • 减少修改字符串带来的内存重分配次数

C:每次字符串修改中需要内存重分配,涉及复杂的算法,可能需要执行系统调用。
SDS:实现了空间预分配和惰性空间释放

空间预分配:用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间。如果free空间满足,则无需扩展。

额外分配的未使用空间数量由以下公式决定:\
if SDS.len < 1MB : 扩展长度=修改后的SDS.len \
例如修改后为13字节,那么len=13 free=13 \
buf的实际长度= 13+13+1 = 27

if SDS.len >= 1MB : 扩展长度=1MB \
例如修改后为30MB 那么len=30MB free=1MB \
buf的实际长度=30MB+1MB+1byte

惰性空间释放:用于优化SDS的字符串缩短操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用。意思就是字符串缩短的时候,内存并不释放,由free记录下来。SDS提供相应的API,允许你真正释放空间。

  • 二进制安全

C:使用空字符串判断字符串的结束,导致无法存储存在空字符串的二进制数据类型。例如文件图像。
SDS:以len判断长度,与空字符串无关。(空字符串的结尾只是为了能使用C的部分函数,避免重复造轮子)

  • 兼容部分C字符串函数

SDS可以重用一部分<string.h>库的定义函数

总结C语言SDS
获取字符串长度的时间复杂度O(N)O(1)
API安全可能造成缓冲区溢出不会造成缓存溢出(内存重分配)
存储只能保存文本数据可以保存文本或者二进制数据

链表

提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活的调整链表的长度。C语言并没有内置这种数据结构,所以redis构建了自己的链表实现。

应用:列表键包含较多元素时/包含元素都是比较长的字符串时;保存多个客户端的状态信息。

链表和链表节点的实现

typedef struct listNode{ 
    // 前置节点 
    struct listNode *prev; 
    // 后置节点 
    struct listNode *next; 
    // 节点的值 
    void *value; 
}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); 
 }list;

dup 函数用于复制链表节点所保存的值
free 函数用于释放链表节点所保存的值
match 函数则用于对比链表节点所保存的值和另一个输入值是否相等

特性:

  • 双端:可直接获取某个节点的前置节点和后置节点的复杂度
  • 无环:表头节点的prev和表尾节点的next指针都指向NULL,对链表的访问是以NULL为终点
  • 带表头和表尾指针
  • 带链表长度计数器
  • 多态:链表节点使用void *指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值