redis 源码学习记录--字符串

学习redis,字符串是最基础的结构之一。

redis字符串设计的比较巧妙。由于他是基于c语言的,不像C++字符串类要实现各种构造函数,析构函数等等,整体设计很精炼。但是也有一些要注意的地方。

字符串有长度len,可用长度free等数据。当然最关键的是数据buf。由于数据不是定长的,所以把len,free放在结构体的最前面,最后才放数据:

struct sdshdr {
	// buf 中已占用空间的长度
	int len;
	// buf 中剩余可用空间的长度
	int free;
	// 数据空间
	char buf[];
};

我们创建一个新的字符串可以这样:

sds c = sdsnew("hello world");

实际上sds是char*的别名。上面那个函数返回的只是一个char指针,那么怎么知道c的长度呢?难不成我们调用strlen,那样设计字符串就没有意义了。上面我们提到长度等数据在buf数据的前面,所以定位len字段就可以知道了,事实上sdslen就是这么实现的:

static inline size_t sdslen(const sds s) {
	struct sdshdr *sh = (void*)(s - (sizeof(struct sdshdr)));
	return sh->len;
}

有个问题需要注意的是,不是所有的char*数据都能这样求长度,只有sdshdr结构的char*数据才能这样做,否则得出的是未知的,因为在char*数据前几个字节的数据是未知的。

客户端对象redisClient的数据缓冲区字段querybuf类型是char*,我们却可以随时对他求长度,那是因为querybuf是用sdsnewlen()创建出来的一个空字符串,在sdsnewlen()中在堆上分配了空间:

sds sdsnewlen(const void *init, size_t initlen) {

	struct sdshdr *sh;

	// 根据是否有初始化内容,选择适当的内存分配方式
	// T = O(N)
	if (init) {
		// zmalloc 不初始化所分配的内存
		sh = zmalloc(sizeof(struct sdshdr) + initlen + 1);
	}
	else {
		// zcalloc 将分配的内存全部初始化为 0
		sh = zcalloc(sizeof(struct sdshdr) + initlen + 1);
	}

	// 内存分配失败,返回
	if (sh == NULL) return NULL;

	// 设置初始化长度
	sh->len = initlen;
	// 新 sds 不预留任何空间
	sh->free = 0;
	// 如果有指定初始化内容,将它们复制到 sdshdr 的 buf 中
	// T = O(N)
	if (initlen && init)
		memcpy(sh->buf, init, initlen);
	// 以 \0 结尾
	sh->buf[initlen] = '\0';

	// 返回 buf 部分,而不是整个 sdshdr
	return (char*)sh->buf;
}

要扩展字符串时,调用的是sds sdsMakeRoomFor(sds s, size_t addlen),他分配新的数据。往数据buf写入数据我们调用strcpy,或者提供buf指针即可,所以我们必须有更新buf关联sdshdr结构的操作,sdsIncrLen:

void sdsIncrLen(sds s, int incr) {
	struct sdshdr *sh = (void*)(s - (sizeof(struct sdshdr)));

	// 确保 sds 空间足够
	assert(sh->free >= incr);

	// 更新属性
	sh->len += incr;
	sh->free -= incr;

	// 这个 assert 其实可以忽略
	// 因为前一个 assert 已经确保 sh->free - incr >= 0 了
	assert(sh->free >= 0);

	// 放置新的结尾符号
	s[sh->len] = '\0';
}

清空数据我们可以调用sdsrange,实际上他是移动数据块的操作:

void sdsrange(sds s, int start, int end) {
	struct sdshdr *sh = (void*)(s - (sizeof(struct sdshdr)));
	size_t newlen, len = sdslen(s);

	if (len == 0) return;
	if (start < 0) {
		start = len + start;
		if (start < 0) start = 0;
	}
	if (end < 0) {
		end = len + end;
		if (end < 0) end = 0;
	}
	newlen = (start > end) ? 0 : (end - start) + 1;
	if (newlen != 0) {
		if (start >= (signed)len) {
			newlen = 0;
		}
		else if (end >= (signed)len) {
			end = len - 1;
			newlen = (start > end) ? 0 : (end - start) + 1;
		}
	}
	else {
		start = 0;
	}

	// 如果有需要,对字符串进行移动
	// T = O(N)
	if (start && newlen) memmove(sh->buf, sh->buf + start, newlen);

	// 添加终结符
	sh->buf[newlen] = 0;

	// 更新属性
	sh->free = sh->free + (sh->len - newlen);
	sh->len = newlen;
}

我们调用sdsrange(s, len, -1),就可以清空结构体的数据,实际是让第一个字节为0,长度变为0,free恢复。

以上就是目前遇到的操作字符串的函数。





猜你喜欢

转载自blog.csdn.net/zxm342698145/article/details/80708415
今日推荐