redis 源代码之数据结构(1)--链表的实现
Remote Dictionary Server)是一种内存Key/Value数据库。所有的Key/Value都是存放在内存中,如果内存不足,会将一些value swap到硬盘,但是Key始终都在内存中。Redis类似于Memcached。但是redis比memcached有更丰富的数据结构,还可以支持备份,数据持久化(snapshot和aof)。
下图是一张是从网上获取的关于redis内部的存储结构(不知道原作者是谁了)。
最近在阅读redis源代码,决定将自己的一些理解记下来,用于备份和检查。无奈技术水平很挫,如果有错误,还希望指正。代码版本是2.6.2,代码量比2.4.17大了很多。==!
1. adlist.h 定义了一个双链表结构。
2. typedef struct listNode {
3. struct listNode *prev;
4. struct listNode *next;
5. void *value;
6. } listNode;
1. typedef struct listIter {
2. listNode *next;
3. int direction;
4. } listIter;
typedef struct list {
1. listNode *head;
2. listNode *tail;
3. void *(*dup)(void *ptr); //用于节点value的copy
4. void (*free)(void *ptr); //用于节点value的释放
5. int (*match)(void *ptr, void *key); //节点value的比较
6. unsigned long len; //链表的长度
7. } list;
adlist 提供的链表操作都是很常见的,节点value的内存分配和释放由用户负责。
list *listInsertNode(list *list, listNode *old_node, void *value, int after) ;//根据after是否为0来决定是在old_node节点之前(after == 0)还是之后(after != 0)
listNode *listIndex(list *list, long index);//返回链表中下标为index的节点,0为head节点,1为head->next节点,以此类推。若index为负数,则从后向前,-1为tail节点,-2为
tail->prev 节点以此类推。
list 数据结构不是太难理解~ 下文将会分析sds数据结构(作者自定义的字符串)
redis 源代码之数据结构(2)--sds实现
1,sds(simple dynamic string)作为redis作者自己实现的字符串类型,是redis的基本数据类型。
1. typedef char *sds;
2.
3. struct sdshdr {
4. int len;
5. int free;
6. char buf[];
7. };
可以看到 sds本质上是一个char指针,内部存储结构为一个header+char*. len表示sds实际占用的空间大小, free表示sds尚未使用的空间。buf指向实际的字符串内容。
sizeof(struct sdshsr)在32位操作系统下面是8,redis作者没有用char *buf,是不是觉得这样一个头部就可以节约4字节内存?char buf[]被gcc编译器理解为动态数组了,而且buf变量只能放在结构体最后位置。否则报错。
2, 关于sds的操作
1)创建sds
sds.c有三个函数用于创建sds
1. sds sdsnewlen(const void *init, size_t initlen);//主要的创建函数
2. sds sdsnew(const char *init);//这个函数实际上调用的sdsnewlen,initlen问 字符串init的大小(不包含最后的‘\0’)
3. sds sdsempty();//同调用sdsnewlen,只不过initlen为0
sdsnewlen的具体代码
1. sds sdsnewlen(const void *init, size_t initlen) {
2. struct sdshdr *sh;
3.
4. if (init) {
5. sizeof(struct sdshdr)+initlen+1); //一个sds真正的占用空间为头部大小+字符串长度
6. else {
7. sizeof(struct sdshdr)+initlen+1);
8. }
9. if (sh == NULL) return NULL;
10. //此处的len,没有把'\0‘计算在内
11. sh->free = 0;
12. if (initlen && init)
13. memcpy(sh->buf, init, initlen);
14. '\0';
15. return (char*)sh->buf; //返回的指针指向真正字符串内容,而不是返回头部指针,这样用户之需要关心真正的内容就行,不需要管理头部
16. }
如果这样调用
sds mysds = sdsnewlen("redis", 5);
那么内存结构图为
那么如何获取头部信息呢,比如说我想获取sds的长度(buf长度)
1. static inline size_t sdslen(const sds s) {
2. struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));//往前偏移
3. return sh->len;
4. }
用sds指针向前移动sizeof(struct sdshdr) 字节(就是减去)就可以指向头部了。从而获取sds的头部相关信息。
2)sds释放
1. void sdsfree(sds s) {
2. if (s == NULL) return;
3. sizeof(struct sdshdr));
4. }
利用redis作者封装的free函数释放掉所有的内存,当然包括头部。
3)sds其他操作这里就不再叙述,基本上常见的字符串的操作都可以找到。