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

来源:互联网 发布:新华网数据新闻 编辑:程序博客网 时间:2024/05/05 00:17

http://blog.csdn.net/lazybin/article/category/1255844

 

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

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

具体的二者区别可以参考http://blog.csdn.net/gpcuster/article/details/5956555 。

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


最近在阅读redis源代码,决定将自己的一些理解记下来,用于备份和检查。无奈技术水平很挫,如果有错误,还希望指正。代码版本是2.6.2,代码量比2.4.17大了很多。==!

[cpp] view plaincopy
  1.  adlist.h 定义了一个双链表结构。  
  2. typedef struct listNode {  
  3. struct listNode *prev;  
  4. struct listNode *next;  
  5. void *value;  
  6. } listNode;  
[cpp] view plaincopy
  1. typedef struct listIter {  
  2. listNode *next;  
  3. int direction;  
  4. } listIter;  
[cpp] view plaincopy
  1. typedef struct list {  
  2. listNode *head;  
  3. listNode *tail;  
  4. void *(*dup)(void *ptr); //用于节点value的copy  
  5. void (*free)(void *ptr); //用于节点value的释放  
  6. int (*match)(void *ptr, void *key); //节点value的比较  
  7. unsigned long len; //链表的长度  
  8. } 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的基本数据类型。

[cpp] view plaincopy
  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

[cpp] view plaincopy
  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的具体代码

[cpp] view plaincopy
  1. sds sdsnewlen(const void *init, size_t initlen) {  
  2.     struct sdshdr *sh;  
  3.   
  4.     if (init) {  
  5.         sh = zmalloc(sizeof(struct sdshdr)+initlen+1);  //一个sds真正的占用空间为头部大小+字符串长度  
  6.     } else {  
  7.         sh = zcalloc(sizeof(struct sdshdr)+initlen+1);  
  8.     }  
  9.     if (sh == NULL) return NULL;  
  10.     sh->len = initlen;                           //此处的len,没有把'\0‘计算在内  
  11.     sh->free = 0;  
  12.     if (initlen && init)  
  13.         memcpy(sh->buf, init, initlen);  
  14.     sh->buf[initlen] = '\0';  
  15.     return (char*)sh->buf;    //返回的指针指向真正字符串内容,而不是返回头部指针,这样用户之需要关心真正的内容就行,不需要管理头部  
  16. }  
如果这样调用

[cpp] view plaincopy
  1. sds mysds = sdsnewlen("redis", 5);  
那么内存结构图为


那么如何获取头部信息呢,比如说我想获取sds的长度(buf长度)

[cpp] view plaincopy
  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释放

[cpp] view plaincopy
  1. void sdsfree(sds s) {  
  2.     if (s == NULL) return;  
  3.     zfree(s-sizeof(struct sdshdr));  
  4. }  
利用redis作者封装的free函数释放掉所有的内存,当然包括头部。

3)sds其他操作这里就不再叙述,基本上常见的字符串的操作都可以找到。


原创粉丝点击