前言 - 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. Kubernetes学习之pause容器

    根据代码看到,pause容器运行着一个非常简单的进程,它不执行任何功能,一启动就永远把自己阻塞住了, 它的作用就是扮演PID1的角色,并在子进程称为"孤儿进程"的时候,通过调用wa ...

  2. Golang 需要避免踩的 50 个坑1

    最近准备写一些关于golang的技术博文,本文是之前在GitHub上看到的golang技术译文,感觉很有帮助,先给各位读者分享一下. 前言 Go 是一门简单有趣的编程语言,与其他语言一样,在使用时不免 ...

  3. 汉诺塔问题深度剖析(python实现)

    当我们学习一门编程语言的时候,都会遇到递归函数这个问题.而学习递归的一个经典案例就是汉诺塔问题.通过这篇文章,观察移动三个盘子和四个盘子的详细过程,您不仅可以深刻的了解递归,也更加熟悉了汉诺塔的游戏的 ...

  4. Comet OJ - Contest #10 C.鱼跃龙门

    传送门 题意: 求最小的\(x\),满足\(\frac{x(x+1)}{2}\% n=0,n\leq 10^{12}\). 多组数据,\(T\leq 100\). 思路: 直接考虑模运算似乎涉及到二次 ...

  5. 201871010102-《面向对象程序设计(java)》第6-7周学习总结

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

  6. JDK1.8 LocalDate 使用方式;LocalDate 封装Util,LocalDate工具类(四)

    未完待续 ........ 前言:       加班了好几天,终于结束上一个坑的项目了,项目交接人员全部离职,代码一行注释没有,无人问津的情况下,完成了项目,所以好的规范真的很重要. 继续日期改写 一 ...

  7. python windows下获取路径时有中文处理

    在windows中用os,path.abspath(__file__)时有中文路径时,默认是转成非unicode格式 这会导致,在其它模块使用该路径时,会报 utf8' codec can't dec ...

  8. selenium webdriver使用的一些小技巧(持续更新中)

    1.开始结束时间只支持控件选择,不支持填写,怎么办? 如下图: 解决方案: 用javaScipt把开始结束时间的reaonly属性去除,然后再输入,举例如下 /**     * 输入开始日期     ...

  9. java http get和post请求

    1.http工具类 package com.funshion.common.utils; import java.net.URI;import java.net.URL; import org.apa ...

  10. axios post方式请求x-ww格式的数据

    //使用axios时,要确定是json格式还是x-www格式的,axios默认是json格式的,如果是x-ww格式需要做如下配置: let url = "/hehe/site/getcomm ...