redis 源代碼之數據結構(sds,鏈表的實現)

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大了很多。==!

  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;  
  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的基本數據類型。

  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.         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. }  
如果這樣調用

  1. 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.     zfree(s-sizeof(struct sdshdr));  
  4. }  
利用redis作者封裝的free函數釋放掉所有的內存,當然包括頭部。

3)sds其他操作這裏就不再敘述,基本上常見的字符串的操作都可以找到。


發佈了8 篇原創文章 · 獲贊 33 · 訪問量 65萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章