都知道redis是通过c语言来编写的,但是c语言里面的字符串修改,存储等有诸多问题,不符合redis的设计思路,所以作者就自己定义了一种简单动态字符串简称SDS.
SDS遵循了C字符串以空格字符结尾的惯例,保存空字符的1字节空间,但是不计算在SDS的len属性里面,并且为空字符串分配额外的1字节的空间,以及添加空字符到字符串末尾等操作,都是有SDS函数完成的,对开发者了来说是透明的,不用关心.
1个字符的\0,不算入字符串的len中,也不做为redis的结束符,只是为了兼容C语言而设计的.
C语言不记录自身长度,所以每次查看字符串长度都需要O(n)的时间复杂度,而SDS则为len直接可以看到,所以复杂度为O(1).
C语言记录自身的长度,所以对于一个包含了N个字符的C字符串来说,C字符串底层实现总是一个N+1个字符长的数组.
所以C语言中字符串的增长或者缩短,都需要进行内存重新分配.
如果修改字符串不太长出现,那么每次修改都执行一次内存重新分配是OK了,但是redis这种数据库修改,删除是很常见的就不太适合了.
内存预分配
字符串长度小于1MB则分配字符串2倍的空间.如果字符串长度大于等于1MB,则内存多分配1MB的空间.
分配内存步骤:如果字符串a=“aaa1111111”(长度为10,按照上面的规则,实际占用空间为21),现在修改a=”aaa1111111bbb"多加了3个字b,那么由于free=10,所以不需要在分配内存了,直接存入就OK了.
修改字符串为增加字符串时,内存空间只有在不够用的时候才重新分配,如果足够,则一直都不需要重新分配.
上面说了内存预分配,那么当字符串减少时呢?
还是a=“aaa1111111”(实际占用21个字符,len=10,free=10,1个\0字符),现在将a中的1移除掉,a=“aaa”了,那么a现在的空间是多少呢? 答案还是(len=3,free=17,1个\0字符).
就是说redis在减少字符串的时候,并不会重新分配内存,而是将空出来的一起做为备用空间.为下一次增加提供优化.
C字符串必须符合某种编码,并且出了字符串的末尾之外,字符串里买呢不能包括空字符,所以C字符串只能保存文本数据,不能保存图片,音频,视频等二进制数据.
SDS的API都是二进制安全的,所有SDS API都会以处理二进制的方式来处理SDS存放在buf数据里的数据.这样你存入什么数据,读取出来之后还是什么数据.
链表的应用是非常广泛的,比如Reids的发布与订阅,满查询,监视器,保存多个客户端的状态信息等功能,都是使用链表来实现的.
链表结构:
typedef struce list{
//头节点
listNode *head;
//尾节点
listNode *tail;
//链表包含的节点数
unsigned long len;
//节点值复制函数
void *(*dup)(void * ptr);
//节点值释放函数
void (*free)(void *ptr);
//节点值对比函数
int (*match)(void *ptr,void *key);
}
//链表结构
typedef struct listNode{
//前节点
struct listNode *prev;
//下一个节点
struct listNode *next;
//值
void *value;
}
链表总结:
字典又称为符号表,关联表,或者映射(map),是一种保存键值对的抽象数据结构.
在字典中的每个key都是独一无二的,程序可以在字典中根据健查找与之关联的值.
redis字典数据结构源码:
typedef struct dict{
//类型特定函数,比如计算hash值的函数,复制函数,键对比函数等.
dictType *type;
//私有数据
void *privdata;
//哈希表
dictht ht[2];
//当rehash不在进行时,值为-1
in trehashidx;
}dict
typedef struct dictht{
//哈希表数组
dictEntry **table;
//哈希表大小(哈希表总的空间大小)
unsigned long size;
//哈希表大小烟吗,用于计算索引值
//sizemask=size-1;
unsigned long sizemask;
//该哈希表已有节点的数量(哈希表中的真实的节点数量)
unsigned long used;
}dictht
typedef struct dictEntry{
//键
void *key;
union{
void *val;
uint64_tu64;
int64_ts64;
}v;
//指向下一个哈希表节点,形成链表
struct dicEntry *next;
} dicEntry
dict.ht[2]: 一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用.
//根据key计算hash值.
hash = dict->type->hashFunction(key);
//使用哈希表的sizemask属性和哈希值,计算出索引值
//根据情况不同,ht[x]可以是ht[0],或者ht[1]
index = hash& dict->ht[x].sizemask;
跳跃表是一种有序的数据结构.跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点.大部分情况下,跳跃表的效率可以和平衡树差不多.
压缩列表(ziplist)是列表键和哈希键的底层实现之一.当一个列表键只包含少量列表项,并且列表项要么是小整数值,要么就是比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现.
压缩列表是Redis为了节约内存而开发的,有一系列特殊编码的连续的内存块组成的顺序型数据结构.一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值.