1. 程式人生 > 資料庫 >redis-sds簡單字串(sds.h、sds.c)

redis-sds簡單字串(sds.h、sds.c)

sds簡單字串(sds.h、sds.c)

部分原始碼解讀(5.0.8版本)

定義結構:

/* 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 */c
char buf[];
};

記憶體結構:

結構體成員變數說明:

len:代表已使用的char陣列長度(不包含結尾\0)

alloc:代表char陣列總長度(不包含\0)

剩餘可使用長度= alloc - len;

使用sds而不使用char*的原因:
1:獲取字串長度為 O(N) 級別的操作 → 因為 C 不儲存陣列的長度,每次都需要遍歷一遍整個陣列

2:字串操作不當拼接容易造成緩衝區溢位/記憶體洩露

3:C字串僅能儲存文字資料,截止\0 則會進行擷取,後邊的無法識別

主要api

函式 作用 時間複雜度
sdsnew 建立一個包含給定C字串的SDS O(N)
sdsempty 建立一個空SDS O(1)
sdsfree 釋放給定的SDS O(N)
sdslen 返回已使用長度 O(1)
sdsavail 返回未使用長度 O(1)
sdsdup 建立給定SDS副本(copy) O(N)
sdscpy 將給定C字串複製到SDS裡面,覆蓋原有的字串 O(N)
sdscmp 比較兩個SDS字串是否相同 O(N)
sdsrange 保留SDS給定區間的資料,非區間內則被覆蓋或清除 O(N)

簡單字串的建立

/* 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;
//根據字串長度選擇型別確定型別(sdshdr8|sdshdr16|sdshdr32|sdshdr64)
char type = sdsReqType(initlen);
/* Empty strings are usually created in order to append. Use type 8
* since type 5 is not good at this. */
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
int hdrlen = sdsHdrSize(type);//頭部長度
unsigned char *fp; /* flags pointer. */
//計算需要申請的記憶體空間並申請記憶體,記憶體大小 = 頭部長度+字串長度+1,後邊+1是因為要補'\0'
sh = s_malloc(hdrlen+initlen+1);
if (sh == NULL) return NULL;//申請失敗返回NULL
if (!init)
memset(sh, 0, hdrlen+initlen+1); //將sh指標指向的地址內容設定為0
s = (char*)sh+hdrlen;//char buf[]陣列指標位置
fp = ((unsigned char*)s)-1;//flag指標
//將*sh 轉為sdhdr型別指標,並將len、alloc、flag進行初始化
switch(type) {
case SDS_TYPE_5: {
*fp = type | (initlen << SDS_TYPE_BITS);
break;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s)//struct sdshdr8 *sh = (struct sdshdr8 *)((s)-(sizeof(struct sdshdr8)));;
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;
}
}
// 將字串拷貝到char buf[]
if (initlen && init)
memcpy(s, init, initlen);
s[initlen] = '\0';//末尾補'\0'
return s;
}

計算字串長度

#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
static inline size_t sdslen(const sds s) { //sds指向buf的位置
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8:
return SDS_HDR(8,s)->len;
case SDS_TYPE_16:
return SDS_HDR(16,s)->len;
case SDS_TYPE_32:
return SDS_HDR(32,s)->len;
case SDS_TYPE_64:
return SDS_HDR(64,s)->len;
}
return 0;
}

說明:

redis進行簡單字串操作時,傳遞的是sds實際上是sdshdr->buf,因為sdshdr泛型出了幾種不同長度的簡單字串,所以通過flag = sds-1可以確定sdshdr的型別,進而獲得對應的sdshdr結構體物件。如果直接傳遞sdshdr不具備通用性,reids 3.0版本之前僅有sdshdr一種型別,使用泛型的好處則避免了記憶體的浪費

redis 2.X.X中簡單字串的定義

struct sdshdr {
int len;
int free;
char buf[];
};
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;

if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
if (sh == NULL) return NULL;
sh->len = initlen;
sh->free = 0;
if (initlen && init)
memcpy(sh->buf, init, initlen);
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}