redis 源代码之数据结构(1)--链表的实现

Remote Dictionary Server)是一种内存Key/Value数据库。所有的Key/Value都是存放在内存中,如果内存不足,会将一些value swap到硬盘,但是Key始终都在内存中。Redis类似于Memcached。但是redis比memcached有更丰富的数据结构,还可以支持备份,数据持久化(snapshot和aof)。

下图是一张是从网上获取的关于redis内部的存储结构(不知道原作者是谁了)。


看完redis源码需要多久 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);

那么内存结构图为

看完redis源码需要多久 redis开源代码_redis_02

那么如何获取头部信息呢,比如说我想获取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其他操作这里就不再叙述,基本上常见的字符串的操作都可以找到。