前言
reids 沒有直接使用C語言傳統的字符串表示(以空字符結尾的字符數組)而是構建了一種名為簡單動態字符串的抽象類型,并為redis的默認字符串表示,因為C字符串不能滿足redis對字符串的安全性、效率以及功能方面的需求
1、SDS 定義
在C語言中,字符串是以'/0'字符結尾(NULL結束符)的字符數組來存儲的,通常表達為字符指針的形式(char *)。它不允許字節0出現在字符串中間,因此,它不能用來存儲任意的二進制數據。
sds的類型定義
typedef char *sds;
每個sds.h/sdshdr結構表示一個SDS的值 struct sdshdr{ //記錄buf數組中已使用的字節的數量 //等于sds所保存字符串的長度 int len; //記錄buf中未使用的數據 int free; //字符數組,用于保存字符串 } * free 屬性的值為0,表示這個SDS沒有分配任何未使用的空間 * len 屬性長度為5,表示這個SDS保存一個五字節長的字符串 * buf 屬性是一個char類型的數組,數組的前5個字節分別保存了'R','e','d','i','s'五個字符,而最后一個字節則保存了空字符串'/0'
肯定有人感到困惑了,竟然sds就等同于char *?
sds和傳統的C語言字符串保持類型兼容,因此它們的類型定義是一樣的,都是char *,在有些情況下,需要傳入一個C語言字符串的地方,也確實可以傳入一個sds。
但是sds和char *并不等同,sds是Binary Safe的,它可以存儲任意二進制數據,不能像C語言字符串那樣以字符'/0'來標識字符串的結束,因此它必然有個長度字段,這個字段在header中
sds的header結構
/* Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[];};struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* used */ uint8_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[];};struct __attribute__ ((__packed__)) sdshdr16 { uint16_t len; /* used */ uint16_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[];};struct __attribute__ ((__packed__)) sdshdr32 { uint32_t len; /* used */ uint32_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[];};struct __attribute__ ((__packed__)) sdshdr64 { uint64_t len; /* used */ uint64_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[];};
SDS一共有5種類型的header。目的是節省內存。
一個SDS字符串的完整結構,由在內存地址上前后相鄰的兩部分組成:
除了sdshdr5之外,其它4個header的結構都包含3個字段:
在各個header的類型定義中,還有幾個需要我們注意的地方:
至此,我們非常清楚地看到了:sds字符串的header,其實隱藏在真正的字符串數據的前面(低地址方向)。這樣的一個定義,有如下幾個好處:
弄清了sds的數據結構,它的具體操作函數就比較好理解了。
sds的一些基礎函數
二、SDS 數組動態分配策略
header信息中的定義這么多字段,其中一個很重要的作用就是實現對字符串的靈活操作并且盡量減少內存重新分配和回收操作。
redis的內存分配策略如下
reids的內存回收策略如下
綜上所述,redis實現的高性能字符串的結果就把N次字符串操作必會發生N次內存重新分配變為人品最差時最多發生N次重新分配。
/* Enlarge the free space at the end of the sds string so that the caller * is sure that after calling this function can overwrite up to addlen * bytes after the end of the string, plus one more byte for nul term. * * Note: this does not change the *length* of the sds string as returned * by sdslen(), but only the free buffer space we have. */sds sdsMakeRoomFor(sds s, size_t addlen) { void *sh, *newsh; size_t avail = sdsavail(s); size_t len, newlen; char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; /* Return ASAP if there is enough space left. */ if (avail >= addlen) return s; len = sdslen(s); sh = (char*)s-sdsHdrSize(oldtype); newlen = (len+addlen); if (newlen < SDS_MAX_PREALLOC) newlen *= 2; else newlen += SDS_MAX_PREALLOC; type = sdsReqType(newlen); /* Don't use type 5: the user is appending to the string and type 5 is * not able to remember empty space, so sdsMakeRoomFor() must be called * at every appending operation. */ if (type == SDS_TYPE_5) type = SDS_TYPE_8; hdrlen = sdsHdrSize(type); if (oldtype==type) { newsh = s_realloc(sh, hdrlen+newlen+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { /* Since the header size changes, need to move the string forward, * and can't use realloc */ newsh = s_malloc(hdrlen+newlen+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; sdssetlen(s, len); } sdssetalloc(s, newlen); return s;} /* Reallocate the sds string so that it has no free space at the end. The * contained string remains not altered, but next concatenation operations * will require a reallocation. * * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */sds sdsRemoveFreeSpace(sds s) { void *sh, *newsh; char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; size_t len = sdslen(s); sh = (char*)s-sdsHdrSize(oldtype); type = sdsReqType(len); hdrlen = sdsHdrSize(type); if (oldtype==type) { newsh = s_realloc(sh, hdrlen+len+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { newsh = s_malloc(hdrlen+len+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; sdssetlen(s, len); } sdssetalloc(s, len); return s;}
三、SDS的特點
sds正是在Redis中被廣泛使用的字符串結構,它的全稱是Simple Dynamic String。與其它語言環境中出現的字符串相比,它具有如下顯著的特點:
四、淺談SDS與string的關系
127.0.0.1:6379> set test testOK127.0.0.1:6379> append test " test"(integer) 9127.0.0.1:6379> get test"test test"127.0.0.1:6379> setbit test 36 1(integer) 0127.0.0.1:6379> get test"test(test"127.0.0.1:6379> getrange test -5 -1"(test"
但是,string除了支持這些操作之外,當它存儲的值是個數字的時候,它還支持incr、decr等操作。它的內部存儲不是SDS,這種情況下,setbit和getrange的實現也會有所不同。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對武林網的支持。
參考文章
新聞熱點
疑難解答