前言 - 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. 踩坑ios H5

    目录 input获取焦点时,页面被放大 ios input输入时白屏 软键盘撑起页面下不来 ios页面滚动不流畅 position:fixed/absolute随屏幕滚动 1.input获取焦点时,页 ...

  2. 获取Object对象属性的方法,Reflect.ownKeys, Object.getOwnPropertyNames,Object.getOwnPropertySymbols,Object.keys,for in

    let triangle={ a:1, b:2, c:3 } function coloTriangle(){ this.color='red'; } coloTriangle.prototype=t ...

  3. ./configure & make & make install 知其所以然

    最近一直在类unix系统上(Ubuntu和Mac OS)上调研第三方的一些开源库,要涉及到开源库的编译安装工作,接触最多的就是./configure & make & make ins ...

  4. python常用库(转)

    转自http://www.west999.com/info/html/wangluobiancheng/qita/20180729/4410114.html Python常用的库简单介绍一下 fuzz ...

  5. XGBoost 参数介绍

    XGBoost 的设置有三种参数:一般参数,提升参数和学习参数. 一般参数 取决于提升器,通常是树或线性模型提升参数 取决于选择的提升器的相关参数学习参数 取决于指定学习任务和相应的学习目标一般参数 ...

  6. 使用apache.tika判断文件类型

    一. 判断文件类型一般可采用两种方式 1. 后缀名判断 简单易操作,但无法准确判断类型 2. 文件头信息判断 通常可以判断文件类型,但有些文件类型无法判断(如word和excel头信息的前几个字节是一 ...

  7. 201871010118-唐敬博《面向对象程序设计(Java)》第二周学习总结

    博文正文开头格式:(3分) 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.co ...

  8. 【java】oracle好用,但java运行缺失右括号

    可能原因SQL拼接有空格被省略导致sql粘连. 解决办法,扩大拼接或者缩小拼接范围.

  9. Scrapy笔记02- 完整示例

    Scrapy笔记02- 完整示例 这篇文章我们通过一个比较完整的例子来教你使用Scrapy,我选择爬取虎嗅网首页的新闻列表. 这里我们将完成如下几个步骤: 创建一个新的Scrapy工程 定义你所需要要 ...

  10. selenium--加载浏览器配置

    前戏 在我们之前写的自动化脚本中,不知道大家有没有发现,每次打开的都是一个新的浏览器(相当于新安装的).但是有时候,我们想打开的是我们配置好的浏览器.我在之前的公司做web自动化的时候,由于我们的网站 ...