您的位置:首页 > 数据库 > Redis

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 设计与实现(第二版)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: