Redis 数据结构(一):简单动态字符串

一、数据结构

redis 为了节省内存,针对不同长度的数据结构采用不同的数据结构。如下共五种,但SDS_TYPE_5 并不使用,因为该类型不会存放数据长度,每次都需要进行分配和释放

#define SDS_TYPE_5	0
#define SDS_TYPE_8	1
#define SDS_TYPE_16	2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

type = 1 为例:

typedef char* sds
// 告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是 GCC 特有的写法
struct __attribute__((__packed__)) sdshdr8 {
uint8_t len; /* 数据长度 */
uint8_t alloc; /* 去掉头和 null 结束符,有效长度+数据长度 */
unsigned char flags;
// 变长数据
char buf[];
}

二、空间扩容

  • 当前有效长度 >= 新增长度,直接返回;
  • 更新之后,判断新旧类型是否一致:
    • 一致使用 remalloc,否则使用 malloc + free
      • 当有效长度 >= 新增长度,直接返回
  • 增长步长:
    • 新增后长度小于预分配长度(1024 * 1024),扩大一倍;
    • 新增后长度大于等于预分配的长度,每次加预分配长度(减少不必要的内存)
sds sdsMakeRoomFor(sds s, size_t addlen){
	// 当前有效长度 >= 新增长度,直接返回
	if(avail > addlen) return s;
	
	len = sdslen(s);
	newLen = len + addLen;
	// 新增后长度小于预分配长度,扩大一倍; SDS_MAX_PREALLOC = 1024 * 1024
	if (newLen < SDS_MAX_PREALLOC) 
		newLen *= 2;
	// 新增后长度大于预分配长度,每次加预分配长度(考虑到内存的占用,可能会 OOM)
	else
		newLen += SDS_MAX_PREALLOC;
	
	type = sdsReqType(newLen);
	
	if (type == SDS_TYPE_5) type = SDS_TYPE_8;
	// 新老类型一直使用 remalloc, 否则使用 malloc + free。 当前有效长度 >= 新增长度,直接返回
	if(oldType == type)
		newsh = s_realloc(sh,hdrlen + newlen + 1);
	else
		// 新老类型不一致需要分配新的内存
		newsh = s_malloc(hdrlen + newlen + 1);
		// 释放老的数据
		s_free(sh);
	sdssetalloc(s, newlen);
	return s
	
}

三、空间缩容

trim 操作时,采用的是惰性空间释放:不会立即使用内存重分配来回收缩短的字节,只是进行移动和标记,并修改数据长度。

sds sdstrim(sds s, const char *cset) {
	char *start, *end, *sp, *ep;
	size_t len;
	sp = start = s;
	ep = end = s + sdslen(s) - 1;
	while(sp <= end && strchr(cset, *sp)) sp++;
	while(ep > sp && strchr(cset, *ep)) ep--;
	len = (sp > ep) ? 0 : ((ep - sp) + 1);
	if(s != sp) remmove(s, sp, len);
	s[len] = '\0';
	sdssetlen(s, len);
	return s;
} 

四、优点 ✅

  • 常量获取字符串长度(len) ➡️ sds结构体中定义了字符串长度。
  • 避免了缓冲区溢出 ➡️ 在扩容时,当 len > SDS_MAX_PREALLOC 时,不是乘以 len 的 2倍,而是加上一个 SDS_MAX_PREALLOC 的大小。
  • 减少字符串修改带来的内存频繁重分配次数 ➡️ 扩容时判断 SDS 类型,若相同则 realloc,若不相同则 malloc 重新分配内存。
  • 二进制操作安全:可以保持文本数据,也可以保持任意格式的二进制数据(如视频流数据)
  • ‘\0’ 结尾,使其兼容部分 C 字符串函数。

五、其他

  • sds 是 char* 的别名,可以理解为分配的是一块连续内存(表头+数据),根据局部性原理可以提高访问速度。
  • 数据存储不使用 SDS_TYPE_5 因为使用该类型每次新数据时,都需要进行扩充
  • 利用 C 语言内存布局,在 SDS 结构体中使用一个 0 长度的数组,既可以达到变长,又能保证内存也是连续的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值