底层实现——结构体
Redis 中的字符串是可以修改的字符串,在内存中它是以字节数组的形式存在的。Redis 中的字符串是可以修改的字符串,在内存中它是以字节数组的形式存在的。
struct SDS<T> {
T capacity; // 数组容量
T len; // 数组长度
byte flags; // 特殊标识位,不理睬它
byte[] content; // 数组内容
}
这里代码作者应该是想表达泛型,但如果底层是用字节数组时,其实根本是不必要的,对于一定是整数的数组容量和长度设置泛型也没什么意义啊。。,而且SDS本身作为一个字符串,用
T[] content也未必合适
字符串的扩容
初次分配,len和capacity一样,但append之后就会触发扩容
APPEND 可以为一系列定长(fixed-size)数据(sample)提供一种紧凑的表示方式,通常称之为时间序列。
/**
*** s 字符串
*** t 将追加到字符串的内容
*** len 追加字符串的长度
**/
sds sdscatlen(sds s, const void *t, size_t len) {
size_t curlen = sdslen(s); // 原字符串长度
// 按需调整空间,如果 capacity 不够容纳追加的内容,就会重新分配字节数组并复制原字符串的内容到新数组中
s = sdsMakeRoomFor(s,len);
if (s == NULL) return NULL; // 内存不足
memcpy(s+curlen, t, len); // 追加目标字符串的内容到字节数组中
sdssetlen(s, curlen+len); // 设置追加后的新长度值
s[curlen+len] = '\0'; // 让字符串以\0 结尾,便于调试打印,还可以直接使用 glibc 的字符串函数进行操作
return s;
}
size_t而不是int:因为当字符串比较短时,len 和 capacity 可以使用 byte 和 short 来表示,Redis 为了对内存做极致的优化,不同长度的字符串使用不同的结构体来表示。
范型是作者抽象出来的,实际Redis源码中是动态的从无符号的char/short/int/longlong中选择使用
Redis 规定字符串的长度不得超过 512M 字节。创建字符串时 len 和 capacity 一样长,不会多分配冗余空间,这是因为绝大多数场景下我们不会使用 append 操作来修改字符串。
embstr vs raw(两种存储方式)
Redis 的字符串有两种存储方式,在长度特别短时,使用 emb 形式存储 (embedded),当长度超过 44 时,使用 raw 形式存储。
Redis 对象头结构体
所有的 Redis 对象都有下面的这个结构头:
struct RedisObject {
int4 type; // 4bits
int4 encoding; // 4bits
int24 lru; // 24bits
int32 refcount; // 4bytes
void *ptr; // 8bytes,64-bit system 对象内容 (body) 的具体存储位置
} robj;
// 共 4*2/8 + 24/8 + 32/8 + 64/8 = 16
当字符串较小的时候,容量和长度可以用1字节表示,即3+ (content).size + 对象头(16)
而内存分配器 jemalloc/tcmalloc 等分配内存大小的单位都是 2、4、8、16、32、64 等等,为了能容纳一个完整的 embstr 对象,jemalloc 最少会分配 32 字节的空间,如果字符串再稍微长一点,那就是 64 字节的空间。
再长的话就是raw,不同于emb将SDS和对象头放在一起,raw将字符数组独立content了出来
勘误
append后,存储方式变化
redis版本4.0.14,无论初始set字符串长度多短,只要append操作,无论追加字符串多短,embstr就会变成raw类型,
SDS结构
struct sdshdr{
//记录buf数组中已使用字节的数量
//等于SDS所保存的字符串长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
文章末尾请带上以下文字及链接:本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情