Redis低级数据结构##
简单动态字符串###
一般的可变字符串用的都是这个,好处就是返回长度和剩余的长度都是O(1)的复杂度,另外自动提供扩容,不会溢出,另外因为free扩容后会自动预分配一些,阈值在不同情况下是不同的,最大多分配1MB的空间,因此减少了重分配次数,另外减少字符串长度时,除了清掉buf[]中的值,还会修改free的值,并不会主动释放内存空间(提供api释放,不会造成浪费)以便使用时不需要再次分配。
struct sdshdr {
//当前长度
int len;
//剩余可用长度
int free;
char buf[];
}
链表###
listNode是一个双向链表对象,list是一个链表的工具类,一般用list。
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
}
typedef struct list{
listNode *head;
listNode *tail;
unsigned long len;
void *(*dup)(void *ptr);
void (*free)(void *ptr);
int (*match)(void *ptr,void *key);
}
字典###
和java的hashmap结构差不多, size代表键值对的数量,sizemask是size-1,用来计算索引的,就是java的hash&length,这里的是hash=dict->type->hashFunction(key) 然后再计算hash&sizemask。然后每个dictEntry是一个单向链表,这里不会像hashmap一样,超过8个就转为rbtree。
然后是扩容过程,扩容标志是dict.trehashidx,-1代表正常,!-1代表扩容中。ht[0]代表正常使用的字典结构,当扩容时会渐进式hash把ht[0]里的值转移到ht[1]里面,此时所有对hash表的操作都在ht[1]里面,扩容完毕后,再把ht[0]释放,将ht[1]设为ht[0],最后重新建一个新的ht[1]
typedef struct dictEntry{
void *key;
union{
void *val;
uint64_t u64;
int64_t s64;
} v
struct dictEntry *next;
}
typedef struct dictht{
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
}
typedef struct dict{
dictType *type;
void *privdata;
dictht ht[2];
int trehashidx;
}
跳跃表###
一种查询效率为O(logn)的数据结构,和平衡二叉树的效率类似。遍历时最慢的就是通过tail->backward 来遍历所有节点.快的就是通过跳跃表的层级来遍历,zskiplistNode.level[]代表了每个节点的层级,索引从小到大代表0级,1级...n级,zskiplistLevel.span代表的意思是两个节点的跨度(配合score来判断该怎么跳),zskiplist.level代表了跳跃表的最大层数。这样如果链表长度很多的时候,就通过level开辟了新的通道来查找元素。
typedef struct zskiplistNode{
struct zskiplistNode *backward;
double score;
robj *obj;
struct zskiplistLevel{
struct zskiplistNode *forward;
unsigned int span;
} level[]
}
typedef struct zskiplist{
struct zskiplistNode *header , *tail;
unsigned long length;
int level;
}
整数集合###
- contents数组的值是有序的,而且不重复。encoding有三种,INTSET_ENC_INT16、INTSET_ENC_INT32、INTSET_ENC_INT64 ,就是contents里面数的范围不一样。int16是-32768
- 32767,int32是-2147483648 ~ 2147483647
另外就是升级的问题,整数集合里面的数据,会自动根据插入的值来升级,比如原来是int16的,然后插入一个int32,那么会遍历所有数据,将int16升为int32,没有降级过程。这样好处是在遍历的时候不会出现类型转换异常,而且也尽量节省了内存。
typedef struct intset{
uint32_t encoding;
uint32_t length;
int8_t contents[];
}
压缩列表###
由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或整数值。
属性 | 类型 | 长度 | 用途 |
---|---|---|---|
zlbytes | uint32_t | 4字节 | 记录整个压缩列表占用的内存字节数:内存重分配或计算zlend位置时使用 |
zltail | uint32_t | 4字节 | 记录尾节点地址和起始地址的内存偏移量,快速定位用的 |
zllen | uint16_t | 2字节 | 记录了节点数量,小于65535时 代表了节点数量, 等于65535时需要遍历才能知道具体数量 |
entryX | 节点 | 不定 | 每个节点,长度由保存的内容决定 |
zlend | uint8_t | 1字节 | 特殊值 0xFF ,标记压缩列表的末端 |
节点的结构
属性 | 长度 | 用途 |
---|---|---|
previous_entry_length | 1或5字节 | 压缩列表中前一个列表的长度,前面长度小于254,则为1字节,否则为5字节,通过zltail和这个就能从后往前遍历所有节点了 |
encoding | 1字节、2字节或5字节 | 记录content中保存的值的属性和类型,00、01、10是字节数组编码,11开头是整数编码 |
content | 由encoding中保存 | 保存节点的值 |
连锁更新
previous_entry_length 一堆1字节的节点堆一块儿了,然后前面插入了一个266字节的节点,那么就会导致新插入节点后面的所有1字节的节点依次更新,删除同理。就是连锁更新。最坏时间复杂度为O(N^2) 但是形成条件苛刻,知道有这回事就行,不用太在意。