redis字符串(String)内存结构:
字符串对象底层数据结构实现为简单动态字符串(SDS)和直接存储,但其编码方式可以是int、raw或者embstr,区别在于内存结构的不同。
-
int编码
字符串保存的是整数值,并且这个正式可以用long类型来表示,那么其就会直接保存在redisObject的ptr属性里,并将编码设置为int -
raw编码
字符串保存的小于44字节的字符串值,则使用简单动态字符串(SDS)结构,并将编码设置为raw,此时内存结构与SDS结构一致,内存分配次数为两次,创建redisObject对象和sdshdr结构。 -
embstr编码
字符串保存的小于等于32字节的字符串值,使用的也是简单的动态字符串(SDS结构),但是内存结构做了优化,用于保存顿消的字符串;内存分配也只需要一次就可完成,分配一块连续的空间即可。
在Redis中,存储long、double类型的浮点数是先转换为字符串再进行存储的
raw与embstr编码效果是相同的,不同在于内存分配与释放,raw两次,embstr一次
embstr 编码的字符串对象的所有数据都保存在一块连续的内存里面, 所以这种编码的字符串对象比起 raw 编码的字符串对象能够更好地利用缓存带来的优势
int编码和embstr编码如果做追加字符串等操作,满足条件下会被转换为raw编码;embstr编码的对象是只读的,一旦修改会先转码到raw
SDS
在redis中,包含字符串的键值都是由SDS实现的
struct sdshdr{
//字节数组
char buf[];
//buf数组中已使用字节数量
int len;
//buf数组中未使用字节数量
int free;
}
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; // 字符串长度
uint8_t alloc; // 分配的空间长度
unsigned char flags; // sds类型 8 16 32 64
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[];
};
为什么使用SDS
-
相比C语言字符串,使获取字符串长度时间复杂度降为O(1)
len 属性
如:访问当前字符串的长度,直接读取即可,时间复杂度0(1)。C语言字符串不记录自身长度,如果想获取自身长度必须遍历整个字符串,对每个字符进行计数,这个操作时间复杂度是O(n)。 -
空间预知分配,惰性空间释放
alloc 属性。
可以用来计算free,用来计算字符串未使用的空间。
空间预分配
SDS修改后,len长度小于1M,那么将会额外分配与len相同的未使用空间。如果修改后长度大于1M,那么将分配1M的使用空间。
惰性释放
有空间分配对应就有空间释放。SDS缩短时,并不会回收多余的内存空间,而是使用free字段将空出来的空间记录下来,如果后续有变更操作,直接使用free,减少了内存的分配次数。 -
二进制安全
二进制数据并不是规划的字符串格式,可能会包含一些特殊字符。比如“\0”,
c中字符串遇到“\0”会结束,那“\0”之后的数据就读取不上了。
SDS根据len长度来判断字符串结束的,二进制安全问题就解决了。
int, emb,raw详解
int
redis 启动时会预先简历 10000个分别存储0-9999的redisObject 变量作为共享对象,这意味着如果set字符窜的键值在0-10000之间的话,则可以直接指向共享对象而不需要建立新对象,此时v 不占空间。
比如 set k1 123 set k2 123
也就是 k1,k2 只想的是同一个RedisObject 对象
与JAVA Integer (-128 to 127)类似。
emb
对于长度小于44的字符串,Redis对键值采用OBJ_ENCODING_EMBSTR方式,EMBSTR表示sds结构体与其对应的redisObject对象分配在同一块连续的内存空间,字符串SDS嵌入在redisObject对象之中一样。实现了内存的连续紧密的空间。
raw
当字符串的键值为长度大于44的超长字符串时,redis则会将键值的内部编码改为OBJ_ENCODING_RAW,这与OBJ_ENCODING_EMBSTR编码方式的不同之处在于,此时动态字符串sds的内存与其依赖的redisObject的内存不在连续了。也就是重新分配了内存。
注意
对于embstr, 由于其实现是只读的,因此在对embstr对象进行修改是,都会先转化为raw,再进行修改,因此,只是修改embstr对对象,修改后的对象一定是raw,无论是否达到了44字节。
比如 set k3 a , append k3 b, 那么k3 则为 raw。