redis-sds簡單字串(sds.h、sds.c)
阿新 • • 發佈:2021-01-06
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;
}