在说字符串之前我们先了解一下redis中的数据结构都是怎么存储的。
在redis中使用一个redisObject数据结构保存所有的键值对形式。
下面是redisObject的定义。
typedef redisObject{
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS;
int refcount;
void* ptr;
}
LRU:least recently used,最远使用的。
LFU:least ferquently used,最少使用
redis中为了存储不同长度的字符串,定义了不同的sds结构体来存储。
有sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64。
其中sdshdr5的结构体较为特殊。
sdshdr5的结构体定义
typedef sdshdr5{
unsigned char flags;
char buf[];
}
sdshdr8的结构体定义,sdshdr16,32,64就只是将len和alloc的数据类型换成了uint16_t,uint32_t,uint64_t
typedef sdshdr8{
uint8_t len;
uint8_t alloc;
unsigned char flags;
char buf[];
}
结构体中各个参数的含义
hdrlen:代表结构体所占空间长度,sdshdr5只用一个char类型的flags。而对于sdshdr8来说还有两个uint8_t类型的参数。
strlen:字符串的长度。
1:用来存储'\0'
s指向buf的头部,addlen代表需要扩容的空间
sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
void *sh, *newsh;
// sds中可用的空间
size_t avail = sdsavail(s);
size_t len, newlen, reqlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
size_t usable;
//可用空间大于addlen时,不用进行扩容
if (avail >= addlen) return s;
//原字符串长度
len = sdslen(s);
//指向sds的结构体
sh = (char*)s-sdsHdrSize(oldtype);
// 新字符串的长度
reqlen = newlen = (len+addlen);
assert(newlen > len); /* Catch size_t overflow */
//SDS_MAX_PREALLOC的大小为1024*1024字节,但新字符串长度超过它时,每次扩容加1MB的空间。否则空间扩容一倍。
if (greedy == 1) {
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
}
// 计算扩容后新的sds结构体类型,不允许使用sdshdr5
type = sdsReqType(newlen);
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
//扩容后结构体的空间
hdrlen = sdsHdrSize(type);
assert(hdrlen + newlen + 1 > reqlen); /* Catch size_t overflow */
if (oldtype==type) {
//扩容后结构体类型没变,使用realloc分配空间
newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {
//扩容后结构体改变,使用malloc重新分配空间
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;
}
OBJ_ENCODING_INT
顾名思义就是将字符串转化为整型数据存储,对于“123456789012”这种数值型字符串使用char数组存储需要占用12个字节,而使用long long类型来存储只用8个字节,可以降低内存的占用。
OBJ_ENCODING_EMBSTR
存储长度小于等于OBJ_ENCODING_EMBSTR_SIZE_LIMIT(44个字节)的字符串。
在该编码格式下,redisObject和sds存储在一片连续的内存块中。
优点在于:
1、redisObject和sds的内存分配和释放只用执行一次。
2、由于使用一片连续的内存块,减少了空间碎片的存在。
OBJ_ENCODING_RAW
存储长度大于OBJ_ENCODING_EMBSTR_SIZE_LIMIT(44个字节)的字符串。
redisObject和sds存储在两个不连续的内存块中。
redis中所有的键都是字符串类型,但是它的编码格式只能为OBJ_ENCODING_RAW和OBJ_ENCODING_EMBSTR
redis中字符串键值对的值在存储时会进行优化,选择合适的编码格式
创建三个字符串类型的键值对。其中value都是数值字符串。
key1:长度为18,对应的编码格式为“int”
key2:长度为30,对应的编码格式为“embstr”
key3:长度为45,对应的编码格式为“raw”
