Redis数据类型之字符串
2015-07-30 18:18
791 查看
Redis数据类型之字符串
redis的字符串
redis的字符串不是C语言原生的字符串,而是自己构建的称为简单动态字符串(simple dynamic string),简称 SDS,和C语言原生的字符串相似,使用’\0’作为结尾。除了打印日志之外,我们操作字符串基本是在使用SDS
SDS的在redis的主要功能
1. 保存数据库的字符串值 2. 用作缓冲区buffer
SDS在redis的定义
在源码包下面的src目录下的sds.h 和sds.c
typedef char *sds; /** * 保存字符串对象的结构 */ struct sdshdr { // buf 中已占用空间的长度 int len; // buf 中剩余可用空间的长度 int free; // 数据空间 char buf[]; };
为什么使用SDS而不是C语言原生的字符串?
SDS有一下特点
获取字符串长度的时间复杂度是O(1)
C语言本省并不携带自身的长度属性,所以要获取长度必须要遍历一次,这个复杂度是O(n)
/** * 返回实际的长度 */ static inline size_t sdslen(const sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); return sh->len; }
防止缓存区溢出
这个由C语言的一些列不安全的字符串操作函数可以看出,比如
strcat(char* dest,const char* src)
看看sds.c中的sdscat()
//函数原型 sds sdscat(sds s, const char *t); //具体实现 sds sdscat(sds s, const char *t) { return sdscatlen(s, t, strlen(t)); } //sdscatlen sds sdscatlen(sds s, const void *t, size_t len) { struct sdshdr *sh; // 原有字符串长度 size_t curlen = sdslen(s); // 扩展 sds 空间 // T = O(N) s = sdsMakeRoomFor(s,len); // 内存不足?直接返回 if (s == NULL) return NULL; // 复制 t 中的内容到字符串后部 // T = O(N) sh = (void*) (s-(sizeof(struct sdshdr))); memcpy(s+curlen, t, len); // 更新属性 sh->len = curlen+len; sh->free = sh->free-len; // 添加新结尾符号 s[curlen+len] = '\0'; // 返回新 sds return s; }
可以看到,sds在进行拼接的时候,首先获取原字符串的长度,然后进行扩展。
3. 减少修改字符串时的内存重新分配
C语言的字符串的首先还是依靠字符数组,长度为N的字符串对应的是一个长度为N+1的字符数组,所以,在对字符串进行操作时,都会发生对字符数组的内存重新分配。
对于内存的重新分配,一般设计复杂的算法和系统调用,相对比较耗时,偶尔一次的重新分配还可以接受,但是对于redis,频繁的操作,肯定效率非常低下。
还记得sds的结构体么?保存了一个未使用的空间。sds通过这个来屏蔽了与底层数组的关联 。
那么sds如何使用这个free来实现减少内存重新分配?
方法一、内存预分配。 优化sds的增长操作
简单的说,就是redis API 在修改sds的时候,如果有涉及对sds长度进行扩展,那么,同时扩展sds的free空间长度。如果free足够,那么就使用free空间。
那么扩展多少?free与length又有什么大小关系?
- 如果扩展之后的sds小于1M,那么,扩展之后的free==length,总的内存大小是 free+length+1byte
- 如果扩展之后的sds大于于1M,那么,free的大小是1M,总长度是1M+length+1byte
方法二、惰性空间释放 优化sds的缩短操作
简单的说,就是redis API 在截断sds的时候,程序并不立即回收多出来的内存,而是使用free属性来保存这些空间。
实现代码
sds sdstrim(sds s, const char *cset) { struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); char *start, *end, *sp, *ep; size_t len; // 设置和记录指针 sp = start = s; ep = end = s+sdslen(s)-1; // 修剪, T = O(N^2) while(sp <= end && strchr(cset, *sp)) sp++; while(ep > start && strchr(cset, *ep)) ep--; // 计算 trim 完毕之后剩余的字符串长度 len = (sp > ep) ? 0 : ((ep-sp)+1); // 如果有需要,前移字符串内容 // T = O(N) if (sh->buf != sp) memmove(sh->buf, sp, len); // 添加终结符 sh->buf[len] = '\0'; // 更新属性 sh->free = sh->free+(sh->len-len); sh->len = len; // 返回修剪后的 sds return s; }
二进制安全
C语言的字符串只能用来保存ANSI字符,因为不能包含空字符。
所有的sds的api都会以处理二进制的方式来处理保存在sds字节数组的数据,不对这些数据进行任何的处理。
兼容部分c语言的字符串。
相关文章推荐
- Android之获取手机上的图片和视频缩略图thumbnails
- android string.xml文件中的整型和string型代替
- Android java 与 javascript互访(相互调用)的方法例子
- redis安装问题小结
- android上改变listView的选中颜色
- String.intern
- Redis偶发连接失败案例实战记录
- Prototype源码浅析 String部分(二)
- Ruby中的String对象学习笔记
- Redis中实现查找某个值的范围
- Redis和Memcached的区别详解
- 分割超大Redis数据库例子
- Redis总结笔记(一):安装和常用命令
- Redis sort 排序命令详解
- redis中修改配置文件中的端口号 密码方法
- 在Ruby on Rails上使用Redis Store的方法
- PostgreSQL ERROR: invalid escape string 解决办法
- Redis和Memcache的区别总结
- 在Node.js应用中使用Redis的方法简介
- Redis服务器的启动过程分析