redis源码分析(2)-- 基本数据结构sds

一、sds格式

sds header定义:

1 struct sdshdr {
2     unsigned int len;
3     unsigned int free;
4     char buf[];
5 };

sizeof(struct sdshdr)= 2*sizeof(unsigned int), char buf[]等价于char buf[0], 仅对编译器有效,并不实际占用存储。

其中len是使用的长度,free是剩余的长度,再加上一个C语言中的‘\0‘结束符

sizeof(buf) = len + free + 1, 格式如下:

二、sds基本操作

1、创建sds对象

 1 sds sdsnewlen(const void *init, size_t initlen) {
 2     struct sdshdr *sh;
 3
 4     if (init) {
 5         sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
 6     } else {
 7         sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
 8     }
 9     if (sh == NULL) return NULL;
10     sh->len = initlen;
11     sh->free = 0;
12     if (initlen && init)
13         memcpy(sh->buf, init, initlen);
14     sh->buf[initlen] = ‘\0‘;
15     return (char*)sh->buf;
16 }
17
18 /* Create a new sds string starting from a null terminated C string. */
19 sds sdsnew(const char *init) {
20     size_t initlen = (init == NULL) ? 0 : strlen(init);
21     return sdsnewlen(init, initlen);
22 }

sdsnew根据输入C字符串init,创建新的sds对象,typedef char *sds; sds即char *类型。

sds s = sdsnew("abc");
当执行上面这行代码,会在堆内存上面开辟一段连续的空间,具体的空间大小是11个字节的空间。
最前面4个字节存的是len ,也就是sds的长度
其次4个字节存的是free,剩余的空间。
最后才是 3个才是存的是我们实际的char的信息
也就是每创建一个sds,都会执行上面的操作,都会申请额外的8个字节才存len和free信息。

既然是一段连续的空间,那么只要已知一个指针,那么就能拿出struct结构里面的任何数据。
还是用上面那个例子
sds s = sdsnew("abc");

这个时候s实际上存的是buf首个char数据的地址。也就是说向前 移8个字节就能到struct sdshdr的len的地址(准确的说是int len的首地址)。8个字节刚好就是struct sdshdr占的空间字节的大小。
所以下面这个是成立的

static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));//把buf的首地址向前移动8个,也是到了len
    return sh->len;
}

s - (sizeof(struct sdshdr)) 表示将指针向前移动到 struct sdshdr 的起点,从而得出一个指向 sdshdr 结构的指针:    

sds中函数比较简单,这里不做一一介绍,感兴趣可以直接阅读sds.c中的源码。

三、sds特点

1、可以通过O(1)获取字符串长度,直接返回sds->len即可,C字符串用strlen需要O(N)

2、杜绝缓冲区溢出。同样因为 sds已经记录长度信息

3、空间预分配和惰性空间释放

sds中空间预分配相关函数:

 1 sds sdsMakeRoomFor(sds s, size_t addlen) {
 2     struct sdshdr *sh, *newsh;
 3     size_t free = sdsavail(s);
 4     size_t len, newlen;
 5
 6     if (free >= addlen) return s; // 需要增加的长度小于free,直接返回,避免了频繁内存申请
 7     len = sdslen(s);
 8     sh = (void*) (s-(sizeof(struct sdshdr)));
 9     newlen = (len+addlen);
10     if (newlen < SDS_MAX_PREALLOC) // 总长度(len+addlen)小于SDS_MAX_PREALLOC(1MB), 新申请长度扩大为2*newlen
11         newlen *= 2;
12     else
13         newlen += SDS_MAX_PREALLOC; // 否则,只比需求量增加1MB
14     newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
15     if (newsh == NULL) return NULL;
16
17     newsh->free = newlen - len;
18     return newsh->buf;
19 }

惰性空间释放用于优化字符串的缩短操作,不用每次缩短都释放内存,仅修改free和len的大小即可。

例如使用sdstrim操作删除sds中的的特定字符,最终只要修改free/len大小,不涉及free操作。

真正需要释放sds中的free空间,可以调用:

1 sds sdsRemoveFreeSpace(sds s) {
2     struct sdshdr *sh;
3
4     sh = (void*) (s-(sizeof(struct sdshdr)));
5     sh = zrealloc(sh, sizeof(struct sdshdr)+sh->len+1);
6     sh->free = 0;
7     return sh->buf;
8 }

通过realloc来重新分配一块不包含free的空间,原空间realloc会自动释放。

4、sds主要api信息

时间: 2024-10-13 14:02:42

redis源码分析(2)-- 基本数据结构sds的相关文章

Redis源码分析(四)-- sds字符串

今天分析的是Redis源码中的字符串操作类的代码实现.有了上几次的分析经验,渐渐觉得我得换一种分析的方法,如果每个API都进行代码分析,有些功能性的重复,导致分析效率的偏低.所以下面我觉得对于代码的分析偏重的是一种功能整体的思维实现来讲解,其中我也会挑出一个比较有特点的方法进行拆分了解,这也可以让我们见识一下里面的一些神奇的代码.好,回归正题,说到字符串,这不管放到哪个编程语言中,都是使用频率极高的操作类.什么new String, concat, strcopy,substr, splitSt

redis源码分析1----字符串sds

1.1 Redis字符串的特点 1. 二进制安全,字符串中间可以包含'\0'字符.  2. redis在字符串的末尾会添加一个'\0'字符,所以redis字符串与标准C的字符串兼容.  3. 根据不同的字符串长度使用不同的类型,并且取消编译过程中的优化对齐,节省空间.  4. 为了提高字符串增长的效率,redis在扩充字符串长度操作(sdsMakeRoomFor)时的预分配了额外的空间,从而再下一次扩充长度时,不用再次分配空间.注意,redis并没有在新建字符串时分配额外的空间.redis基于一

redis源码分析之内存布局

redis源码分析之内存布局 1. 介绍 众所周知,redis是一个开源.短小.高效的key-value存储系统,相对于memcached,redis能够支持更加丰富的数据结构,包括: 字符串(string) 哈希表(map) 列表(list) 集合(set) 有序集(zset) 主流的key-value存储系统,都是在系统内部维护一个hash表,因为对hash表的操作时间复杂度为O(1).如果数据增加以后,导致冲突严重,时间复杂度增加,则可以对hash表进行rehash,以此来保证操作的常量时

redis源码分析4---结构体---跳跃表

redis源码分析4---结构体---跳跃表 跳跃表是一种有序的数据结构,他通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的: 跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点.性能上和平衡树媲美,因为事先简单,常用来代替平衡树. 在redis中,只在两个地方使用了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构. 1 跳跃表节点 1.1 层 层的数量越多,访问其他节点的速度越快: 1.2 前进指针 遍历举例

redis源码分析3---结构体---字典

redis源码分析3---结构体---字典 字典,简单来说就是一种用于保存键值对的抽象数据结构: 注意,字典中每个键都是独一无二的:在redis中,内部的redis的数据库就是使用字典作为底层实现的: 1 字典的实现 在redis中,字典是使用哈希表作为底层实现的,一个hash表里面可以有多个hash表节点,而每个hash表节点就保存了字典中的一个键值对: hash表定义 table属性是一个数组,数组中的每个元素都是一个指向dictEntry结构的指针,每个dictEntry结构保存着一个键值

redis 源码分析(一) 内存管理

一,redis内存管理介绍 redis是一个基于内存的key-value的数据库,其内存管理是非常重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中统一使用zmalloc,zfree一系列函数,其对应的源码在src/zmalloc.h和src/zmalloc.c两个文件中,源码点这里. 二,redis内存管理源码分析 redis封装是为了屏蔽底层平台的差异,同时方便自己实现相关的函数,我们可以通过src/zmalloc.h 文件中的相关宏定义

redis源码分析之事务Transaction(下)

接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令. 原文地址:http://www.jianshu.com/p/e22615586595 看本篇文章前需要先对上面文章有所了解: redis源码分析之事务Transaction(上) 一.redis事务核心命令简介 redis事务操作核心命令: //用于开启事务 {"multi",multiCommand,1,"sF",0,NULL,0,0,0,0,0}, //用来执行事

redis源码解析之dict数据结构

dict 是redis中最重要的数据结构,存放结构体redisDb中. typedef struct dict { dictType *type; void *privdata; dictht ht[2]; int rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */ } dict; 其中type是特定结构的处

redis源码分析(1)--makefile和目录结构分析

一.redis源码编译 redis可以直接在官网下载(本文使用版本 3.0.7):https://redis.io/download 安装: $ tar xzf redis-3.0.7.tar.gz $ cd redis-3.0.7 $ make make执行以后主要编译产物在src/redis-server src/redis-cli 如果想把redis-server直接install到可执行目录/usr/local/bin,还需要执行: $ make install Run Redis wi

Redis源码分析(一)--Redis结构解析

从今天起,本人将会展开对Redis源码的学习,Redis的代码规模比较小,非常适合学习,是一份非常不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的.希望最终能把他啃完吧,C语言好久不用,快忘光了.分析源码的第一步,先别急着想着从哪开始看起,先浏览一下源码结构,可以模块式的渐入,不过比较坑爹的是,Redis的源码全部放在在里面的src目录里,一下90多个文件统统在里面了,所以我选择了拆分,按功能拆分,有些文件你看名字就知道那是干什么的.我拆分好后的而结果如下: 11个包,这样每