前言 - Simple Dynamic Strings

   antirez 想统一 Redis,Disque,Hiredis 项目中 SDS 代码, 因此构建了这个项目

https://github.com/antirez/sds . 更多介绍的背景知识, 可以阅读 README.md.

  sds 项目是 C 字符串数据结构在实际环境中一种取舍实现, 库本身是非线程安全的. 下面

来带大家手写相关代码, 透彻了解这个库的意图(antirez 注释的很棒).

#define SDS_MAX_PREALLOC  (1024*1024)

/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
}; struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
}; struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
}; struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
}; struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
}; #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
#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
#define SDS_HDR(T, s) ((struct sdshdr##T *)((s) - (sizeof(struct sdshdr##T))))
#define SDS_HDR_VAR(T, s) struct sdshdr##T * sh = SDS_HDR(T, s)

可以先分析 sdshdr5 sdshdr8 sdshdr16 sdshdr32 sdshdr64, 感受作者的意图.

首先这几个基本结构, 都有 len, alloc, flags, buf 4 个字段, 是不是. 有的同学会问, sdshdr5

中没有 alloc 和 len 字段呀,  这个啊 sdshdr5 结构比较特殊, 其 alloc 和 len 都隐含在 flags

中, 三者复用一个字段. 可以从函数宏 SDS_TYPE_5_LEN(f), 可以看出来.  因而 sdshdr5 表达的字符

串长度和字符串容量默认相同.  再考究一下 __attribute__ ((__packed__)) 意图(告诉编译器取消结

构在编译过程中的优化对齐, 按照实际占用字节数进行对齐). 对于取消结构内存编译对齐优化, 我的考究有

两点, 1 节约内存, 2 内存可移植变强.

随后多数是流水线代码, 非常好理解. 例如有段这样关系的代码 sdsalloc() = sdsavail() + sdslen()

inline size_t sdslen(const sds s) {
unsigned char flags = s[-];
switch (flags & SDS_TYPE_MASK) {
case SDS_TYPE_5 :
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8 :
return SDS_HDR( , s)->len;
case SDS_TYPE_16:
return SDS_HDR(, s)->len;
case SDS_TYPE_32:
return SDS_HDR(, s)->len;
case SDS_TYPE_64:
return SDS_HDR(, s)->len;
}
return ;
} inline size_t sdsavail(const sds s) {
unsigned char flags = s[-];
switch (flags & SDS_TYPE_MASK) {
case SDS_TYPE_8 : {
SDS_HDR_VAR( , s);
return sh->alloc - sh->len;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(, s);
return sh->alloc - sh->len;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(, s);
return sh->alloc - sh->len;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(, s);
return sh->alloc - sh->len;
}
default:
return ;
}
} /* sdsalloc() = sdsavail() + sdslen() */
inline size_t sdsalloc(const sds s) {
unsigned char flags = s[-];
switch (flags & SDS_TYPE_MASK) {
case SDS_TYPE_5 :
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8 :
return SDS_HDR( , s)->alloc;
case SDS_TYPE_16:
return SDS_HDR(, s)->alloc;
case SDS_TYPE_32:
return SDS_HDR(, s)->alloc;
case SDS_TYPE_64:
return SDS_HDR(, s)->alloc;
}
return ;
}

是不是一下就理解了 sdsalloc(), sdsavail(), sdslen() 是干什么的呢 ❤

正文 - 代码抽样讲解

1. 重复代码可以修的更好

/* Helper for sdscatlonglong() doing the actual number -> string
* conversion. 's' must point to a string with room for at least
* SDS_LLSTR_SIZE bytes.
*
* The function returns the length of the null-terminated string
* representation stored at 's'. */
#define SDS_LLSTR_SIZE 21
int sdsll2str(char *s, long long value) {
char *p, aux;
unsigned long long v;
size_t l; /* Generate the string representation, this method produces
* an reversed string. */
v = (value < ) ? -value : value;
p = s;
do {
*p++ = ''+(v%);
v /= ;
} while(v);
if (value < ) *p++ = '-'; /* Compute length and add null term. */
l = p-s;
*p = '\0'; /* Reverse the string. */
p--;
while(s < p) {
aux = *s;
*s = *p;
*p = aux;
s++;
p--;
}
return l;
} /* Identical sdsll2str(), but for unsigned long long type. */
int sdsull2str(char *s, unsigned long long v) {
char *p, aux;
size_t l; /* Generate the string representation, this method produces
* an reversed string. */
p = s;
do {
*p++ = ''+(v%);
v /= ;
} while(v); /* Compute length and add null term. */
l = p-s;
*p = '\0'; /* Reverse the string. */
p--;
while(s < p) {
aux = *s;
*s = *p;
*p = aux;
s++;
p--;
}
return l;
}

long long or unsigned long long convert to char * 中, 功能很简单. 函数结尾处代码

重复不少, 是可以重构复用的.

inline int sdsreverse(char * s, char * p) {
/* Compute length and add null term. */
size_t l = p - s;
*p = '\0'; p--;
while (s < p) {
char aux = *s;
*s = *p;
*p = aux; s++;
p--;
} return (int)l;
} /* Helper for sdscatlonglong() doing the actual number -> string
* conversion. 's' must point to a string with room for at least
* SDS_LLSTR_SIZE bytes.
*
* The function returns the length of the null-terminated string
* representation stored at 's'. */
#define SDS_LLSTR_SIZE 21
int sdsll2str(char * s, long long value) {
char * p;
unsigned long long v; /* Generate the string representation, this method produces
* an reversed string. */
v = (value < ) ? -value : value;
p = s;
do {
*p++ = '' + (v % );
} while ((v /= ));
if (value < ) *p++ = '-'; return sdsreverse(s, p);
} /* Identical sdsll2str(), but for unsigned long long type. */
int sdsull2str(char * s, unsigned long long v) {
/* Generate the string representation, this method produces
* an reversed string. */
char * p = s;
do {
*p++ = '' + (v % );
} while ((v /= )); return sdsreverse(s, p);
}

是不是显得老学究气质凸显了不少.

2.  vsnprintf 用的太硬邦邦

/* Like sdscatprintf() but gets va_list instead of being variadic. */
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char staticbuf[], *buf = staticbuf, *t;
size_t buflen = strlen(fmt)*; /* We try to start using a static buffer for speed.
* If not possible we revert to heap allocation. */
if (buflen > sizeof(staticbuf)) {
buf = s_malloc(buflen);
if (buf == NULL) return NULL;
} else {
buflen = sizeof(staticbuf);
} /* Try with buffers two times bigger every time we fail to
* fit the string in the current buffer size. */
while() {
buf[buflen-] = '\0';
va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy);
va_end(cpy);
if (buf[buflen-] != '\0') {
if (buf != staticbuf) s_free(buf);
buflen *= ;
buf = s_malloc(buflen);
if (buf == NULL) return NULL;
continue;
}
break;
} /* Finally concat the obtained string to the SDS string and return it. */
t = sdscat(s, buf);
if (buf != staticbuf) s_free(buf);
return t;
}

由 while vsnprintf 来扩容, 这实在太暴力. 更好操作可以观看 man vsnprintf/ 这里可以看我的提交

https://github.com/antirez/sds/pull/115/commits/51e2cd78b1a102055979ec9eb83766b8d2cd6927

/* Like sdscatprintf() but gets va_list instead of being variadic. */
sds sdscatvprintf(sds s, const char * fmt, va_list ap) {
int size;
va_list cpy;
char staticbuf[], * buf, * t; /* Determine required size */
va_copy(cpy, ap);
size = vsnprintf(NULL, , fmt, cpy);
va_end(cpy); if (size < ) return NULL; /* For '\0' */
size++; /* We try to start using a static buffer for speed.
* If not possible we revert to heap allocation. */
if (size > sizeof(staticbuf)) {
buf = s_malloc(size);
if (buf == NULL) return NULL;
} else {
buf = staticbuf;
} va_copy(cpy, ap);
size = vsnprintf(buf, size, fmt, cpy);
va_end(ap); if (size < ) {
if (buf != staticbuf) s_free(buf);
return NULL;
} /* Finally concat the obtained string to the SDS string and return it. */
t = sdscat(s, buf);
if (buf != staticbuf) s_free(buf);
return t;
}

别问, 问就是上天.

3. sdssplitargs 难以让人见色起意

/* Helper function for sdssplitargs() that returns non zero if 'c'
* is a valid hex digit. */
int is_hex_digit(char c) {
return (c >= '' && c <= '') || (c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F');
} /* Helper function for sdssplitargs() that converts a hex digit into an
* integer from 0 to 15 */
int hex_digit_to_int(char c) {
switch(c) {
case '': return ;
case '': return ;
case '': return ;
case '': return ;
case '': return ;
case '': return ;
case '': return ;
case '': return ;
case '': return ;
case '': return ;
case 'a': case 'A': return ;
case 'b': case 'B': return ;
case 'c': case 'C': return ;
case 'd': case 'D': return ;
case 'e': case 'E': return ;
case 'f': case 'F': return ;
default: return ;
}
} /* Split a line into arguments, where every argument can be in the
* following programming-language REPL-alike form:
*
* foo bar "newline are supported\n" and "\xff\x00otherstuff"
*
* The number of arguments is stored into *argc, and an array
* of sds is returned.
*
* The caller should free the resulting array of sds strings with
* sdsfreesplitres().
*
* Note that sdscatrepr() is able to convert back a string into
* a quoted string in the same format sdssplitargs() is able to parse.
*
* The function returns the allocated tokens on success, even when the
* input string is empty, or NULL if the input contains unbalanced
* quotes or closed quotes followed by non space characters
* as in: "foo"bar or "foo'
*/
sds *sdssplitargs(const char *line, int *argc) {
const char *p = line;
char *current = NULL;
char **vector = NULL; *argc = ;
while() {
/* skip blanks */
while(*p && isspace(*p)) p++;
if (*p) {
/* get a token */
int inq=; /* set to 1 if we are in "quotes" */
int insq=; /* set to 1 if we are in 'single quotes' */
int done=; if (current == NULL) current = sdsempty();
while(!done) {
if (inq) {
if (*p == '\\' && *(p+) == 'x' &&
is_hex_digit(*(p+)) &&
is_hex_digit(*(p+)))
{
unsigned char byte; byte = (hex_digit_to_int(*(p+))*)+
hex_digit_to_int(*(p+));
current = sdscatlen(current,(char*)&byte,);
p += ;
} else if (*p == '\\' && *(p+)) {
char c; p++;
switch(*p) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'b': c = '\b'; break;
case 'a': c = '\a'; break;
default: c = *p; break;
}
current = sdscatlen(current,&c,);
} else if (*p == '"') {
/* closing quote must be followed by a space or
* nothing at all. */
if (*(p+) && !isspace(*(p+))) goto err;
done=;
} else if (!*p) {
/* unterminated quotes */
goto err;
} else {
current = sdscatlen(current,p,);
}
} else if (insq) {
if (*p == '\\' && *(p+) == '\'') {
p++;
current = sdscatlen(current,"'",);
} else if (*p == '\'') {
/* closing quote must be followed by a space or
* nothing at all. */
if (*(p+) && !isspace(*(p+))) goto err;
done=;
} else if (!*p) {
/* unterminated quotes */
goto err;
} else {
current = sdscatlen(current,p,);
}
} else {
switch(*p) {
case ' ':
case '\n':
case '\r':
case '\t':
case '\0':
done=;
break;
case '"':
inq=;
break;
case '\'':
insq=;
break;
default:
current = sdscatlen(current,p,);
break;
}
}
if (*p) p++;
}
/* add the token to the vector */
vector = s_realloc(vector,((*argc)+)*sizeof(char*));
vector[*argc] = current;
(*argc)++;
current = NULL;
} else {
/* Even on empty input string return something not NULL. */
if (vector == NULL) vector = s_malloc(sizeof(void*));
return vector;
}
} err:
while((*argc)--)
sdsfree(vector[*argc]);
s_free(vector);
if (current) sdsfree(current);
*argc = ;
return NULL;
} /* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */
void sdsfreesplitres(sds *tokens, int count) {
if (!tokens) return;
while(count--)
sdsfree(tokens[count]);
s_free(tokens);
}

sdssplitargs 函数开始不好理解, 不过我这写了个 demo 分享给大家

#include <stdio.h>

#include "sds.h"

int main(int argc, char * argv[]) {
int count;
const char * line = " hset name \"name:filed\" \"value:field\" ";
sds * tokens = sdssplitargs(line, &count); printf("line = [%s], count = [%d]\n", line, count);
for (int i = ; i < count; i++) {
printf("tokens[%d] = [%s]\n", i, tokens[i]);
} sdsfreesplitres(tokens, count); return ;
}

输出

line = [ hset name "name:filed" "value:field" ], count = []
tokens[] = [hset]
tokens[] = [name]
tokens[] = [name:filed]
tokens[] = [value:field]

是不是瞬间开悟 -> sdssplitargs 到底要闹那样!

整体写下来, 感受是 antirez sds 实现的很四平八稳, 没有什么花哨的地方.

感兴趣的朋友写写, 收获少不了 ~

后记 - 再接再厉

错误是难免, 欢迎交流吹水 ~

责无旁贷  - https://music.163.com/#/song?id=1363553512

古朗月行(唐·李白)
小时不识月,呼作白玉盘。
又疑瑶台镜,飞在青云端。
仙人垂两足,桂树作团团。
白兔捣药成,问言与谁餐。
蟾蜍蚀圆影,大明夜已残。
羿昔落九乌,天人清且安。
阴精此沦惑,去去不足观。
忧来其如何,凄怆摧心肝。

C基础 带你手写 redis sds的更多相关文章

  1. C基础 带你手写 redis adlist 双向链表

    引言 - 导航栏目 有些朋友可能对 redis 充满着数不尽的求知欲, 也许是 redis 属于工作, 交流(面试)的大头戏, 不得不 ... 而自己当下对于 redis 只是停留在会用层面, 细节层 ...

  2. C基础 带你手写 redis ae 事件驱动模型

    引言 - 整体认识 redis ae 事件驱动模型, 网上聊得很多. 但当你仔细看完一篇又一篇之后, 可能你看的很舒服, 但对于 作者为什么要这么写, 出发点, 好处, 缺点 ... 可能还是好模糊, ...

  3. 关于布隆过滤器,手写你真的知其原理吗?让我来带你手写redis布隆过滤器。

    说到布隆过滤器不得不提到,redis, redis作为现在主流的nosql数据库,备受瞩目:它的丰富的value类型,以及它的偏向计算向数据移动属性减少IO的成本问题.备受开发人员的青睐.通常我们使用 ...

  4. 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍

    概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...

  5. Tensorflow编程基础之Mnist手写识别实验+关于cross_entropy的理解

    好久没有静下心来写点东西了,最近好像又回到了高中时候的状态,休息不好,无法全心学习,恶性循环,现在终于调整的好一点了,听着纯音乐突然非常伤感,那些曾经快乐的大学时光啊,突然又慢慢的一下子出现在了眼前, ...

  6. 带小伙伴手写 golang context

    前言 - context 源码 可以先了解官方 context.go 轮廓. 这里捎带保存一份当前 context 版本备份. // Copyright 2014 The Go Authors. Al ...

  7. 用C、python手写redis客户端,兼容redis集群 (-MOVED和-ASK),快速搭建redis集群

    想没想过,自己写一个redis客户端,是不是很难呢? 其实,并不是特别难. 首先,要知道redis服务端用的通信协议,建议直接去官网看,博客啥的其实也是从官网摘抄的,或者从其他博客抄的(忽略). 协议 ...

  8. 手写redis的docker文件,通过docker-compose配置redis

    在前面一遍随笔,配置的是mysql主从的docker-compose配置.今天我们来学习配置编排容器redis. 准备环境: docker 18.06.1-ce docker-compose 1.23 ...

  9. 带你手写基于 Spring 的可插拔式 RPC 框架(二)整体结构

    前言 上一篇文章中我们已经知道了什么是 RPC 框架和为什么要做一个 RPC 框架了,这一章我们来从宏观上分析,怎么来实现一个 RPC 框架,这个框架都有那些模块以及这些模块的作用. 总体设计 在我们 ...

随机推荐

  1. 默认展开ztree树形菜单

    var setting = { view: { selectedMulti: false //按住ctrl是否可以多选 }, check: { enable: true , chkStyle: 'ch ...

  2. QML 缓存(QML Caching)

    最近一直在学习QML,在Qt Creator中新建Qt Quick工程后,尝试不断地修改qml文件来进行试验,qml文件需要反反复复的修改. Qt Widget工程,如果修改了cpp代码后,直接构建或 ...

  3. MySQL单表数据不要超过500万行:是经验数值,还是黄金铁律?

    本文阅读时间大约3分钟. 梁桂钊 | 作者 今天,探讨一个有趣的话题:MySQL 单表数据达到多少时才需要考虑分库分表?有人说 2000 万行,也有人说 500 万行.那么,你觉得这个数值多少才合适呢 ...

  4. Python基础(一)--变量、注释、文件头

    一.变量 1.什么是变量 变量是变化的量,用来保存程序执行的状态以及状态的变化 2.变量定义规范 #1. 变量名只能是:字母.数字或下划线的任意组合 #2. 变量名的第一个字符不能是数字 #3. 关键 ...

  5. CentOS 8 安装

    截止目前为止CentOS的最新版本为CentOS 8版本,接下来就介绍CentOS Linux 8.0.1905的安装过程 1. 安装CentOS 8 成功引导系统会显示如上图的界面: # 界面说明 ...

  6. 让configure和cmake编译时支持调试选项

    在Linux先编译软件基本都是采用configure文件生成makefile,或者,cmake生成makefile文件两种方式.它们生成的makefile文件,一般默认不支持-g调试选项.但我们使用这 ...

  7. Python--RE--?

    ?在re中默认匹配前一个字符0次或者1次 比如: aal? 默认匹配aal,或者aa    即整体匹配前一个字符串,但是可以舍弃最近的一个字符或者不舍弃 re模块 常用正则表达式符号 '.' 默认匹配 ...

  8. 目标检测论文解读3——Fast R-CNN

    背景 deep ConvNet兴起,VGG16应用在图像分类任务上表现良好,本文用VGG16来解决检测任务.SPP NET存在CNN层不能fine tuning的缺点,且之前的方法训练都是分为多个阶段 ...

  9. Python实现斐波那契数列,九九乘法表,金字塔方法。

    斐波那契数列普通函数实现 #普通函数 def fb(max): a,b=0,1 while a<max: print(a) a,b=b,a+b fb(100) 递归实现方法1 def fb1(m ...

  10. vmware中桥接模式,NAT模式,主机模式的区别

    桥接模式 在桥接模式下,VMWare虚拟出来的操作系统就像是局域网中的一台独立的主机(主机和虚拟机处于对等地 位),它可以访问网内任何一台机器.在桥接模式下,我们往往需要为虚拟主机配置IP地址.子网掩 ...