redis原始碼分析(二)、sds動態字串學習總結
sds字串
Redis 只會使用 C 字串作為字面量, 在大多數情況下, Redis 使用 SDS (Simple Dynamic String,簡單動態字串)作為字串表示。 比起 C 字串, SDS 具有以下優點:
- 常數複雜度獲取字串長度。
- 杜絕緩衝區溢位。
- 減少修改字串長度時所需的記憶體重分配次數。
- 二進位制安全。
- 相容部分 C 字串函式。
根據傳統, C 語言使用長度為N+1的字元陣列來表示長度為 N 的字串,並且字元陣列的最後一個元素總是空字元 '\0' 。
C 語言使用的這種簡單的字串表示方式, 並不能滿足 Redis 對字串在安全性、效率、以及功能方面的要求, 本節接下來的內容將詳細對比 C 字串和 SDS 之間的區別, 並說明 SDS 比 C 字串更適用於 Redis 的原因。
SDS又叫簡單動態字串,在Redis中預設使用SDS來表示字串。比如在Redis中的鍵值對中的鍵一般都是使用SDS來實現。首先需要說明的是在Redis中,字串不是用傳統的字串來實現,而是Redis自己構建了一個結構來表示字串。優點如下:
1、O(1)時間內獲取字串長度。常數複雜度獲取字串長度(依據其結構特性,只需要訪問其結構體成員len既可獲得字串長度)
2、杜絕緩衝區溢位,另外SDS還提供的一些API操作,是二進位制安全的(也就是不會因為空格等特殊字元而中斷字串)、不會溢位(API操作會檢查其長度)
3、減少了修改字串時帶來的記憶體重分配次數。
- 對於增長字串其採用的策略是檢查修改之後的長度大小,如果小於1024*1024,則分配2倍的修改後的長度+1
- 對於減少的字串其並不立即釋放空間,而是迴歸到alloc中去。(自動查閱redis記憶體分配與釋放策略)
typedef char *sds;//底層用於儲存字串的資料結構SDS
/* 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 {
//低三位儲存type,高5位儲存長度
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表示的字串是有SDS header和char*指標組成,而SDS的頭部主要由四部分組成:
- len:SDS字串已使用的空間。
- alloc:申請的空間,減去len就是未使用的空間,初始時和len一致。
- flag:只使用了低三位表示型別,細化了SDS的分類,根據字串的長度的不同選擇不同的sds結構體,而結構體的主要區別是len和alloc的型別,這樣做可以節省一部分空間大小,畢竟在redis字串非常多,進一步的可以節省空間。
- buf: 用了C的特性表示不定長字串。
除了sdshdr5之外,其他結構都是相似的。我們先看看sdshdr,它只有flags和buf成員,其中flag空間被C充分利用,其第三位儲存了SDS字串的型別.
#define SDS_TYPE_MASK 7 // 型別掩碼
#define SDS_TYPE_BITS 3
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); // 獲取header頭指標
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) // 獲取header頭指標
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) // 獲取sdshdr5的長度,低三位儲存了type
/*sds結構一共有五種Header定義,其目的是為了滿足不同長度的字串可以使用不同大小的Header,從而節省記憶體。
Header部分主要包含以下幾個部分:len、alloc、flags其中
len:表示字串真正的長度,不包含空終止字和alloc以及flag
alloc:表示字串的最大容量,不包含Header和最後的空終止字元和flag
flag:只用了3位表示sds的type 就是表示header的型別*/
// 五種header型別,flags取值為0~4
#define SDS_TYPE_5 0
#define SDS_TYPE_8 1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
sdsnewlen函式
sds建立函式
/* Create a new sds string with the content specified by the 'init' pointer
* and 'initlen'.
* If NULL is used for 'init' the string is initialized with zero bytes.
*
* The string is always null-termined (all the sds strings are, always) so
* even if you create an sds string with:
*
* mystring = sdsnewlen("abc",3);
*
* You can print the string with printf() as there is an implicit \0 at the
* end of the string. However the string is binary safe and can contain
* \0 characters in the middle, as the length is stored in the sds header. */
sds sdsnewlen(const void *init, size_t initlen) {
void *sh;
sds s;
// 根據initlen 求sds使用的結構體型別
char type = sdsReqType(initlen);
//下面是我在測試的時候新增的列印字串型別程式碼,原始碼中沒有
int type_t = type;
printf("sds.c------->sdsnewlen: the sds type is: [ %d ]\n", type_t);
/* Empty strings are usually created in order to append. Use type 8
* since type 5 is not good at this. */
//type5 不再使用 而是直接使用type8
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
//sds使用的結構體的大小
int hdrlen = sdsHdrSize(type);
unsigned char *fp; /* flags pointer. */ //flag的第三位表示sds使用的結構體
//分配內層 大小是sds結構體的大小: hdrlen + buf的大小initlen + '\0'
sh = s_malloc(hdrlen+initlen+1);
//init為null的時候 直接初始化sh內容
if (!init)
memset(sh, 0, hdrlen+initlen+1);
//sh 分配失敗的情況直接返回null
if (sh == NULL) return NULL;
// s為資料部分的起始指標 指向buf地址指標
s = (char*)sh+hdrlen;
//fp指向flag flag的低三位表示sds使用的結構體型別
fp = ((unsigned char*)s)-1;
//根據sds使用的結構體型別 給結構體中的成員賦值
switch(type) {
case SDS_TYPE_5: {
// initlen << SDS_TYPE_BITS 把initlen的值儲存到flag的高5位中去 低三位儲存type的值
*fp = type | (initlen << SDS_TYPE_BITS);
break;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
}
if (initlen && init)
//拷貝資料部分
memcpy(s, init, initlen);
// 與C字串相容
s[initlen] = '\0';
// 返回建立的sds字串指標
return s;
}
sdsMakeRoomFor函式
函式原型:sds sdsMakeRoomFor(sds s, size_t addlen)
- 說明:實現擴充已有sds的可用空間為指定的大小,擴充規則是:當addlen的長度小於1024*1024時,則申請的空間是2*(addlen+len),否則擴充為1024*1024大小。
- 返回值:擴充後的sds物件
/* 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; //獲取type
int hdrlen;
/* Return ASAP if there is enough space left. */
if (avail >= addlen) return s; //如果可用空間大於addlen直接返回舊的字串
len = sdslen(s); //求sds的長度
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
//根據newlen調整sds的type
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;
//根據type獲取sds使用的結構體的長度
hdrlen = sdsHdrSize(type);
//若型別和原有型別一樣,則採用realloc分配空間,否則重新分配採用malloc函式分配空間
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中的字串拷貝到newsh指向的buf中去
s_free(sh);//釋放舊的sh空間
s = (char*)newsh+hdrlen; //s指向重新分配的字串
s[-1] = type; //指定newsds的type
sdssetlen(s, len); //設定新的字串的長度
}
sdssetalloc(s, newlen); //設定新的newsh的分配的空間長度
return s;
}
**sdscatlen **:sds提供了字串的連線函式,用來連線兩個字串
//sds字串連線
sds sdscatlen(sds s, const void *t, size_t len) {
size_t curlen = sdslen(s);
//擴充套件s的空間
s = sdsMakeRoomFor(s,len);
if (s == NULL) return NULL;
// 連線新字串
memcpy(s+curlen, t, len);
// 設定連線後字串長度
sdssetlen(s, curlen+len);
s[curlen+len] = '\0';
return s;
}
sds sdsempty(void); // 清空sds
sds sdsdup(const sds s); // 複製字串
sds sdsgrowzero(sds s, size_t len); // 擴充套件字串到指定長度
sds sdscpylen(sds s, const char *t, size_t len); // 字串的複製
sds sdscpy(sds s, const char *t); // 字串的複製
sds sdscatfmt(sds s, char const *fmt, ...); //字串格式化輸出
sds sdstrim(sds s, const char *cset); //字串縮減
void sdsrange(sds s, int start, int end); //字串擷取函式
void sdsupdatelen(sds s); //更新字串最新的長度
void sdsclear(sds s); //字串清空操作
void sdstolower(sds s); //sds字元轉小寫表示
void sdstoupper(sds s); //sds字元統一轉大寫
sds sdsjoin(char **argv, int argc, char *sep); //以分隔符連線字串子陣列構成新的字串
sdsull2str 把一個long long的型別的數轉成字串
#include <iostream>
using namespace std;
int sdsll2str(char *s, long long value) {
char *p, aux;
unsigned long long v;
size_t l;
/* Generate the string representation, this method produces
* an reversed string. */
v = (value < 0) ? -value : value;
p = s;
do {
*p++ = '0'+(v%10);
v /= 10;
} while(v);
if (value < 0) *p++ = '-';
/* Compute length and add null term. */
l = p-s;
*p = '\0';
/* Reverse the string. */
p--;
while(s < p) {
aux = *s;
*s = *p;
*p = aux;
s++;
p--;
}
// std::cout << s << std::endl;
return l;
}
int main()
{
char str[10]={0};
sdsll2str(str, 1234567);
std::cout << str << std::endl;
}
相關文章
- Redis原始碼之SDS簡單動態字串Redis原始碼字串
- SDS-redis動態字串Redis字串
- Redis原始碼閱讀:Redis字串SDSRedis原始碼字串
- Redis—簡單動態字串(SDS)Redis字串
- Redis【2】- SDS原始碼分析Redis原始碼
- Redis原始碼閱讀:sds字串實現Redis原始碼字串
- 深入理解Redis 資料結構—簡單動態字串sdsRedis資料結構字串
- SpringCloud Alibaba Nacos 配置動態更新原始碼學習總結SpringGCCloud原始碼
- 簡單動態字串(simple dynamic string)SDS字串
- 見微知著——Redis字串內部結構原始碼分析Redis字串原始碼
- redis學習總結Redis
- Redis動態字串Redis字串
- Redis sds資料結構實現分析ZFRedis資料結構
- 【Redis 系列】redis 學習十五,redis sds資料結構和底層設計原理Redis資料結構
- 【MyBatis學習總結 (五),動態SQL】MyBatisSQL
- Activity 啟動流程學習總結(附原始碼流程圖)原始碼流程圖
- Redis學習總結1Redis
- mediakit 原始碼 輕微微 學習總結原始碼
- PHP 學習總結之字串PHP字串
- [redis]SDS和連結串列Redis
- Redis 設計與實現 3:字串 SDSRedis字串
- 字串學習總結(Hash & Manacher & KMP)字串KMP
- 跟著大彬讀原始碼 - Redis 7 - 物件編碼之簡單動態字串原始碼Redis物件字串
- Android Sensor原始碼分析總結Android原始碼
- 考研學習總結(二)
- Redis 資料結構 之 SDSRedis資料結構
- redis原始碼學習之slowlogRedis原始碼
- redis個人原始碼分析筆記3---redis的事件驅動原始碼分析Redis原始碼筆記事件
- Mybatis 原始碼學習(二)MyBatis原始碼
- 學習RadonDB原始碼(二)原始碼
- redis string 簡單動態字串Redis字串
- Redis基礎知識(學習筆記11--SDS)Redis筆記
- redis資料結構原始碼閱讀——字串編碼過程Redis資料結構原始碼字串編碼
- JAVA基礎學習-數字與字串學習總結Java字串
- Java容器原始碼學習--ArrayList原始碼分析Java原始碼
- Redis資料結構概覽(原始碼分析)Redis資料結構原始碼
- SpringBoot原始碼學習4——SpringBoot內嵌Tomcat啟動流程原始碼分析Spring Boot原始碼Tomcat
- Hadoop學習——Client原始碼分析Hadoopclient原始碼
- [Redis 系列]redis 學習二Redis