前言

项目里用到了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] = '\0'; // 只返回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] = '\0'; return s;
} /**
* 将一个char数组拼接到sds 末尾
*/
sds sdscat(sds s, const char *t)
{
return sdscatlen(s, t, strlen(t));
}

OK,这里暂时对sds(简单动态字符串)的学习告一段落,继续写业务逻辑代码,很好奇hashs和sets结构是如何实现!!

Redis设计与实现读书笔记——简单动态字符串的更多相关文章

  1. 【笔记】《Redis设计与实现》chapter2 简单动态字符串

    ------------恢复内容开始------------ 2.1 SDS的定义 struct sdshdr{ // 记录buf数组中已使用字节的数量 // 等于SDS所保存字符串的长度(不含'\0 ...

  2. 小白的Redis学习(一)-SDS简单动态字符串

    本文为读<Redis设计与实现>的记录.该书以Redis2.9讲解Redis相关内容.请注意版本差异. Redis使用C语言实现,他对C语言中的char类型数据进行封装,构建了一种简单动态 ...

  3. Redis源码解析:01简单动态字符串SDS

    Redis没有直接使用C字符串(以'\0'结尾的字符数组),而是构建了一种名为简单动态字符串( simple  dynamic  string, SDS)的抽象类型,并将SDS用作Redis的默认字符 ...

  4. Redis 设计与实现读书笔记一 Redis字符串

    1 Redis 是C语言实现的 2 C字符串是 /0 结束的字符数组 3 Redis具体的动态字符串实现 /* * 保存字符串对象的结构 */ struct sdshdr { // buf 中已占用空 ...

  5. Redis设计与实现读书笔记(二) 链表

    链表作为最基础的数据结构,在许多高级语言上已经有了很好的实现.由于redis采用C语言编写,需要自己实现链表,于是redis在adlist.h定义了链表类型.作者对于这部分没什么好说,源码比较简单,如 ...

  6. Redis设计与实现读书笔记(一) SDS

    作为redis最基础的底层数据结构之一,SDS提供了许多C风格字符串所不具备的功能,为之后redis内存管理提供了许多方便.它们分别是: 二进制安全 减少字符串长度获取时间复杂度 杜绝字符串溢出 减少 ...

  7. <<redis设计和实现>>读书笔记

    redis如何实现主从同步的高效率?? 主从复制的同步有一个命令数据的同步文本,然后利用两个不同服务器的偏移量来进行进行同步,避免每次都是全部同步(并非会保存所有的命令数据,而是会有一个缓冲区(比如1 ...

  8. Redis设计与实现读书笔记——双链表

    前言 首先,贴一下参考链接: http://www.redisbook.com/en/latest/internal-datastruct/adlist.html, 另外真赞文章的作者,一个90后的小 ...

  9. Redis 设计与实现读书笔记一 Redis List

    list结构体 adlist.h/list(源码位置) /* * 双端链表结构 */ typedef struct list { // 表头节点 listNode *head; // 表尾节点 lis ...

随机推荐

  1. js constructor 和 instanceof

    说到这两个属性,那不得不说一下_proto_这个属性,通常这个属性是隐藏属性,是不允许被暴露的,而某些浏览器为了开发者能够很好的理解,而将这个属性暴露出来,比如Mozilla FireFox,这就是为 ...

  2. python面向对象中类对象、实例对象、类变量、实例变量、类方法、实例方法、静态方法

    1. 类对象和实例对象 Python中一切皆对象,Python类本身也是一种对象,类定义完成后,会在当前作用域中定义一个以类名为名字的命名空间.类对象具有以下两种操作: 可以通过“类名()”的方式实例 ...

  3. JAVAEE——宜立方商城04:图片服务器FastDFS、富文本编辑器KindEditor、商品添加功能完成

    1. 学习计划 1.图片上传 a) 图片服务器FastDFS b) 图片上传功能实现 2.富文本编辑器的使用KindEditor 3.商品添加功能完成 2. 图片服务器的安装 1.存储空间可扩展. 2 ...

  4. [java] java中的初始化顺序

    先看程序: package init_cls; class A{ {System.out.println("i am in the class A!");} static { Sy ...

  5. mysql查询语句 和 多表关联查询 以及 子查询

    原文地址: http://blog.csdn.net/github_37767025/article/details/67636061 1.查询一张表: select * from 表名: 2.查询指 ...

  6. 算法转AI平台工程师记录-0

    --- vim源码安装: 1. git clone https://github.com/vim/vim.git 2. cd vim && ./configure --prefix=x ...

  7. 【POJ】2151:Check the difficulty of problems【概率DP】

    Check the difficulty of problems Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 8903   ...

  8. 拆分Cocos2dx渲染部分代码

    纹理实现 思想 这个是Cocos2dx的渲染部分的最基本的实现,被我拆分到mac上,但是并不是用的EGLContext,而是搭配glfw,还有soil第三方图形库. 实现 // // main.cpp ...

  9. Java中日期类型和mysql中日期类型进行整合

      1. java与mysql中日期.时间类型总结: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 mysql(版本:5.1.50)的时间日期类型如下:   da ...

  10. Codeforces Round #361 (Div. 2) B. Mike and Shortcuts bfs

    B. Mike and Shortcuts 题目连接: http://www.codeforces.com/contest/689/problem/B Description Recently, Mi ...