redis学习笔记(1)---字符串sds
2016-05-03 19:13
489 查看
字符串
在C语言中,字符串通常有以下两种方式来表示:char *buf1="redis"; char buf2[]="redis";
buf1是通过一个char指针指向一个字符串字面量,其内容是不能改变的,即不能使用buf1[3]=’c’;这种方式来改变字符串中的某个字符,要改变字符串内容只能通过给buf1指针重新赋值,因此不能重用buf1指向的内存空间。
buf2是一个char数组,末尾会自动有一个字节的’\0’表示结束,其长度为6个字节。其内容虽然可以改变,但是由于buf2本身不携带长度等信息,因此调用一些字符串操作如strcat时,可能会导致缓冲区溢出,不安全。
redis中没有使用C语言的这两种方式来表示字符串,而是通过一种名为简单动态字符串(simple dynamic string,SDS)的类型来表示的。
C字符串只会作为字符串字面量在一些无需修改的地方用到,如打印日志:
redisLogFromHandler(REDIS_WARNING, "You insist... exiting now.");
在redis中,sds除了可以保存数据库的字符串键值对外,还可以用作AOF缓冲区、客户端状态中的缓冲区。
SDS
1、sds的定义
struct sdshdr { unsigned int len;//buf中已用长度 unsigned int free;//buf中剩余可用长度 char buf[];//保存字符串的数组 }; // sizeof(sdshdr) = 8
示例如下:
buf总长度为11个字节,其中5个字节已用(len),5个字节未用(free),buf末尾总会有1个字节的’\0’表示字符串的结束,这一个字节不会计入SDS的长度属性中。
2、sds的创建、复制、清除与释放
创建:sds sdsnewlen(const void *init, size_t initlen) { struct sdshdr *sh; if (init) { sh = zmalloc(sizeof(struct sdshdr)+initlen+1);//+1用于存储'\0' } else { sh = zcalloc(sizeof(struct sdshdr)+initlen+1); } if (sh == NULL) return NULL; sh->len = initlen;//设置len和free的值 sh->free = 0; if (initlen && init) //将init中的内容copy到sds中 memcpy(sh->buf, init, initlen); sh->buf[initlen] = '\0';//字符串结束符 return (char*)sh->buf; } // 调用sdsnew来创建一个sds字符串,其中字符串的内容为init sds sdsnew(const char *init) { size_t initlen = (init == NULL) ? 0 : strlen(init); return sdsnewlen(init, initlen); }
这样就完成了一个字符串的创建了,返回的是保存字符串的buf的起始地址。
复制:
sds sdsdup(const sds s) { return sdsnewlen(s, sdslen(s)); }
清除:
void sdsclear(sds s) { struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); sh->free += sh->len; sh->len = 0; sh->buf[0] = '\0'; }
释放:
void sdsfree(sds s) { if (s == NULL) return; zfree(s-sizeof(struct sdshdr)); }
3、sds的长度获取
static inline size_t sdslen(const sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); return sh->len; } static inline size_t sdsavail(const sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); return sh->free; }
由于sds在函数间传递时,使用的都是字符串的起始地址buf,因此sds结构体的起始地址为s-(sizeof(struct sdshdr))。
找到sds结构体的起始地址后,就可以直接获取相关字段的值了。
因此获取len和free的操作的时间复杂度都是O(1)。
4、sds空间扩展
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))); newlen = (len+addlen); if (newlen < SDS_MAX_PREALLOC)//长度扩展方法 newlen *= 2; else newlen += SDS_MAX_PREALLOC; newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);//重新分配内存 if (newsh == NULL) return NULL; newsh->free = newlen - len; return newsh->buf; }
通过上面的代码可以发现,sds空间扩展不是直接将空间扩展为newlen = sds->len+addlen的,而是在此基础上,再将newlen扩大2倍。如sds->len = 10, addlen=20,则扩展后的新sds字符串的所有可用空间的长度为(10+20)*2=60字节。
SDS的优势:
1. sds获取字符串长度的时间复杂度为O(1),sds->len。而C字符串为O(N)2、sds能防止缓冲区溢出。C字符串在调用strcat等操作时,并不会进行安全性检查,因此可能会导致缓冲区溢出。而sds在操作前都会对缓冲区进行检查
3、减少修改字符串时带来的内存重分配的次数
4、二进制安全,C字符串遇到空格即认为字符串结束,因此C字符串只能保存不含空格的字符串。而sds字符串有一个长度字段指示字符串的总长度,因此不会有这个限制
本文所引用的源码全部来自Redis3.0.7版本
redis学习参考资料:
https://github.com/huangz1990/redis-3.0-annotated
Redis 设计与实现(第二版)
相关文章推荐
- linux下php redis扩展安装
- 阿里云Redis云数据库用户交流会
- redis 基本命令学习三(键值相关命令及服务器相关命令)
- Java中使用Jedis操作Redis
- 在windows上部署使用Redis
- redis的aof持久化深入解析
- Redis启动多端口、运行多实例
- zabbix监控redis多实例(low level discovery)
- Thinkphp S方法的redis配置
- yum 安装redis
- 五、SpringBoot JPA 配置redis
- Redis 起步
- Redis脚本使用总结
- redis 部分操作
- Redis命令详解
- Redis高级特性及应用场景
- redis
- elk+redis 搭建nginx日志分析平台
- redis配置
- redis入门简介