传统的C实现的字符串char*的不足之处?
- 1、以'\0'为字符串结尾,无法实现任意的字符串的保存,会被截断。
- 2、无法进行高效的操作,例如获取字符串长度、比较、追加等操作。
- 3、无法保存二进制类型。
- 4、操作复杂,追加,创建等操作涉及到内存的分配。
redis设计的SDS的优点?
- 1、高效的操作。redis以高效为名,高效的数据结构是保障。设计高效的字符串操作的数据接口。
- 2、保存任意字符串。C中的字符串实现以'/0'为结尾,无法保存任意的字符串。
- 3、保存二进制类型。
- 4、动态管理。C中的字符串操作需要自己管理内存的申请和释放,redis设计了自己的数据结构,在内部封装了对内存的操作,不需要自己维护。同时提供了动态扩容,惰性删除等操作。
- 5、节省内存。redis区分了4种不同大小的SDS类型,分别是SDSHDR8、SDSHDR16、SDSHDR32、SDSHDR64、其区别在于其结构体内的len和alloc,表示字符串的最大长度。通过attribute((packed))来告诉编译器通过紧凑的方式来分配内存(一般编译器都是按照8字节对齐的方式来分配内存)。
SDS源码分析
SDSHDR5不会被使用。
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;/*8s[-1]z直接获取到了SDS中的flags*/
int hdrlen;
size_t usable;
/* Return ASAP if there is enough space left. */
if (avail >= addlen) return s;
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
assert(newlen > len); /* Catch size_t overflow */
if (newlen < SDS_MAX_PREALLOC)/*小于1M,则申请需要内存的两倍*/
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;/*否则申请内存为所需内存➕1M*/
type = sdsReqType(newlen);/*计算最新长度所需的SDS类型*/
/* 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 5*/
hdrlen = sdsHdrSize(type);
assert(hdrlen + newlen + 1 > len); /* Catch size_t overflow */
if (oldtype==type) {
newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {/*如果是新的类型,则涉及到字符串的拷贝,此时会存在短暂的旧内存消耗,所以SDS不应该保存较大内存的数据*/
/* Since the header size changes, need to move the string forward,
* and can't use realloc */
newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
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);
}
usable = usable-hdrlen-1;
if (usable > sdsTypeMaxSize(type))
usable = sdsTypeMaxSize(type);
sdssetalloc(s, usable);
return s;
}
sds sdsRemoveFreeSpace(sds s) {
void *sh, *newsh;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
size_t len = sdslen(s);
size_t avail = sdsavail(s);
sh = (char*)s-oldhdrlen;
/* Return ASAP if there is no space left. */
if (avail == 0) return s;
/* Check what would be the minimum SDS header that is just good enough to
* fit this string. */
type = sdsReqType(len);
hdrlen = sdsHdrSize(type);
/* If the type is the same, or at least a large enough type is still
* required, we just realloc(), letting the allocator to do the copy
* only if really needed. Otherwise if the change is huge, we manually
* reallocate the string to use the different header type. */
if (oldtype==type || type > SDS_TYPE_8) {/*如果类型不变或者需要较大的内存时,只是调用了s_realloc()不做内存的真正释放*/
newsh = s_realloc(sh, oldhdrlen+len+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+oldhdrlen;
} 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;
}
redis中SDS的使用
SDS作为redis非常基础的数据结构使用非常的广泛:
- 键。
- 客户端输入缓冲区。
- slowlog。
……