zoukankan      html  css  js  c++  java
  • Redis设计与实现读书笔记——简单动态字符串

    前言

    项目里用到了redis数据结构,不想只是简单的调用api,这里对我的读书笔记做一下记录。原文地址: http://www.redisbook.com/en/latest/internal-datastruct/sds.html

    数据类型定义

    与sds实现有关的数据类型有两个,一个是 sds:

    // 字符串类型的别名
    typedef char *sds;
    

    另一个是 sdshdr:

    // 持有sds的结构
    struct sdshdr {
    	// buf中已经被使用的字符串空间数量
    	int len;
    	// buf中预留字符串的空间数量
    	int free;
    	// 实际存储字符串的地方
    	char buf[];
    };

    其中,sds只是字符串数组类型char*的别名,而sdshdr用于持有和保存sds的信息

    比如,sdshdr.len可以用于在O(1)的复杂度下获取sdshdr.buf中存储的字符串的实际长度,而sdshdr.free则用于保存sdshdr.buf中还有多少预留空间

    (这里sdshdr应该是sds handler的缩写)

    将sdshdr用作sds

    sds模块对sdshdr结构使用了一点小技巧:通过指针运算,它使得sdshdr结构可以像sds类型一样被传值和处理,并在需要的时候恢复成sdshdr类型

    通过下面的函数定义来理解这个技巧

    sdsnewlen 函数返回一个新的sds值,实际上,它创建的却是一个sdshdr结构:

    sds sdsnewlen(const void *init, size_t initlen)
    {
    	struct sdshdr *sh;
    
    	if (init) {
    		// 创建
    		sh = malloc(sizeof(struct sdshdr) + initlen + 1);
    	} else {
    		// 重分配
    		sh = calloc(1, sizeof(struct sdshdr) + initlen + 1);
    	}
    
    	if (sh == NULL) return NULL;
    
    	sh->len = initlen;
    	sh->free = 0;	// 刚开始free为0
    
    	if (initlen && init) {
    		memcpy(sh->buf, init, initlen);
    	}
    	sh->buf[initlen] = '';
    
    	// 只返回sh->buf这个字符串部分
    	return (char *)sh->buf;
    }
    

    通过使用变量持有一个sds的值,在遇到那些只处理sds值本身的函数时,可以直接将sds传给它们。比如说,sdstoupper 函数就是其中的一个例子:

    static inline size_t sdslen(const sds s)
    {
    	// 从sds中计算出相应的sdshdr结构
    	struct sdshdr *sh = (void *)(s - (sizeof(struct sdshdr)));
    
    	return sh->len;
    }
    
    
    void sdstoupper(sds s)
    {
    	int len = sdslen(s), j;
    
    	for (j = 0; j < len; j ++)
    		s[j] = toupper(s[j]);
    }
    

    这里有一个技巧,通过指针运算,可以从sds值中计算出相应的sdshdr结构:

    sds虽然是指向char *的buf(ps:并且空数组不占用内存空间,数组名即为内存地址),但是分配的时候是分配sizeof(struct sdshdr) + initlen + 1的,通过sds - sizeof(struct sdshdr)可以计算出struct sdshdr的首地址,从而可以得到len和free的信息




    sdsavail 函数就是使用这中技巧的一个例子:

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

    内存分配函数实现

    和Reids 的实现决策相关的函数是 sdsMakeRoomFor :

    sds sdsMakeRoomFor(sds s, size_t addlen)
    {
    	struct sdshdr *sh, *newsh;
    	size_t free = sdsavail(s);
    	size_t len, newlen;
    
    	// 预留空间可以满足本地拼接	
    	if (free >= addlen) return s;
    
    	len = sdslen(s);
    	sh = (void *)(s - (sizeof(struct sdshdr)));
    
    	// 设置新sds的字符串长度
    	// 这个长度比完成本次拼接实际所需的长度要大
    	// 通过预留空间优化下次拼接操作
    	newlen = (len + addlen);
    	if (newlen < 1024 * 1024)
    		newlen *= 2;
    	else
    		newlen += 1024;
    
    	// 重新分配sdshdr
    	newsh = realloc(sh, sizeof(struct sdshdr) + newlen + 1);
    	if (newsh == NULL) return NULL;
    
    	newsh->free = newlen - len;
    
    	// 只返回字符串部分
    	return newsh->buf;
    }

    这种内存分配策略表明,在对sds 值进行扩展(expand)时,总会预留额外的空间,通过花费更多的内存,减少了对内存进行重分配(reallocate)的次数,并优化下次扩展操作的处理速度

    再把redis的如果实现对sds字符串扩展的方法贴一下,很不错的思路:

    /**
     * 按长度len扩展sds,并将t拼接到sds的末尾
     */
    sds sdscatlen(sds s, const void *t, size_t len)
    {
    	struct sdshdr *sh;
    
    	size_t curlen = sdslen(s);
    
    	// O(N)
    	s = sdsMakeRoomFor(s, len);
    	if (s == NULL) return NULL;
    
    	// 复制
    	memcpy(s + curlen, t, len);
    
    	// 更新len和free属性
    	sh = (void *)(s - (sizeof(struct sdshdr)));
    	sh->len = curlen + len;
    	sh->free = sh->free - len;
    
    	// 终结符
    	s[curlen + len] = '';
    
    	return s;
    }
    
    /**
     * 将一个char数组拼接到sds 末尾
     */
    sds sdscat(sds s, const char *t)
    {
    	return sdscatlen(s, t, strlen(t));
    }


    OK,这里暂时对sds(简单动态字符串)的学习告一段落,继续写业务逻辑代码,很好奇hashs和sets结构是如何实现!!
  • 相关阅读:
    后端Golang+前端React架构开发案例
    Vim技巧大全
    Draggable Modal dialog in Bootstrap
    Github.com的镜像站
    kettle之excel上传数据库
    自定义函数之分割函数
    jmeter 压测 ActiveMq 消息队列
    SQL---查找+删除重复记录
    异常值检测(Outlier Detection)
    使用u盘在pc上安装centos7(安装停留在dracut:/#的处理)
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/3177887.html
Copyright © 2011-2022 走看看