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

1.1 Redis字符串的特点

  1. 二进制安全,字符串中间可以包含‘\0‘字符。

   2. redis在字符串的末尾会添加一个‘\0‘字符,所以redis字符串与标准C的字符串兼容。

   3. 根据不同的字符串长度使用不同的类型,并且取消编译过程中的优化对齐,节省空间。

   4. 为了提高字符串增长的效率,redis在扩充字符串长度操作(sdsMakeRoomFor)时的预分配了额外的空间,从而再下一次扩充长度时,不用再次分配空间。注意,redis并没有在新建字符串时分配额外的空间。redis基于一个假设:如果一个字符串有了一次扩充长度的操作,那么它之后很可能也会扩充长度,因此在这时预分配空间。对于redis来说,绝大部分字符串是不需要扩充长度的,因此在创建字符串时并不预分配空间。

 1 struct __attribute__ ((__packed__)) sdshdr5 {
 2     unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
 3     char buf[];
 4 };
 5 struct __attribute__ ((__packed__)) sdshdr8 {
 6     uint8_t len; /* used */
 7     uint8_t alloc; /* excluding the header and null terminator */
 8     unsigned char flags; /* 3 lsb of type, 5 unused bits */
 9     char buf[];
10 };
11 struct __attribute__ ((__packed__)) sdshdr16 {
12     uint16_t len; /* used */
13     uint16_t alloc; /* excluding the header and null terminator */
14     unsigned char flags; /* 3 lsb of type, 5 unused bits */
15     char buf[];
16 };
17 struct __attribute__ ((__packed__)) sdshdr32 {
18     uint32_t len; /* used */
19     uint32_t alloc; /* excluding the header and null terminator */
20     unsigned char flags; /* 3 lsb of type, 5 unused bits */
21     char buf[];
22 };
23 struct __attribute__ ((__packed__)) sdshdr64 {
24     uint64_t len; /* used */
25     uint64_t alloc; /* excluding the header and null terminator */
26     unsigned char flags; /* 3 lsb of type, 5 unused bits */
27     char buf[];
28 };

   __attrubte__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行分配。

1.2  实现中使用的技巧

  1.2.1 计算结构体的地址

   在使用字符串的时候,我们并不会经常使用字符串结构体(例如sdshdr32),而是会使用其中的字符串量(buf),因此需要根据buf找到结构体的地址

1 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
2 #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
3 #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

  

  1.2.2 根据buf计算flags的地址

  利用下标为负数来获取flags的地址

 1 static inline void sdssetalloc(sds s, size_t newlen) {
 2     unsigned char flags = s[-1];
 3     switch(flags&SDS_TYPE_MASK) {
 4         case SDS_TYPE_5:
 5             /* Nothing to do, this type has no total allocation info. */
 6             break;
 7         case SDS_TYPE_8:
 8             SDS_HDR(8,s)->alloc = newlen;
 9             break;
10         case SDS_TYPE_16:
11             SDS_HDR(16,s)->alloc = newlen;
12             break;
13         case SDS_TYPE_32:
14             SDS_HDR(32,s)->alloc = newlen;
15             break;
16         case SDS_TYPE_64:
17             SDS_HDR(64,s)->alloc = newlen;
18             break;
19     }
20 }

  1.2.3 存储空间预分配

 1 sds sdsMakeRoomFor(sds s, size_t addlen) {
 2     void *sh, *newsh;
 3     size_t avail = sdsavail(s);
 4     size_t len, newlen;
 5     char type, oldtype = s[-1] & SDS_TYPE_MASK;
 6     int hdrlen;
 7
 8     /* Return ASAP if there is enough space left. */
 9     if (avail >= addlen) return s;
10
11     len = sdslen(s);
12     sh = (char*)s-sdsHdrSize(oldtype);
13     newlen = (len+addlen);
14     // 如果小于SDS_MAX_PREALLOC,则分配的新长度为请求长度的2倍
15     if (newlen < SDS_MAX_PREALLOC)
16         newlen *= 2;
17     else
18         /* 否则加上SDS_MAX_PREALLOC */
19         newlen += SDS_MAX_PREALLOC;
20
21     // 根据新长度计算新类型
22     type = sdsReqType(newlen);
23
24     /* Don‘t use type 5: the user is appending to the string and type 5 is
25      * not able to remember empty space, so sdsMakeRoomFor() must be called
26      * at every appending operation. */
27     if (type == SDS_TYPE_5) type = SDS_TYPE_8;
28
29     hdrlen = sdsHdrSize(type);
30     if (oldtype==type) {
31         // 新旧类型相同,则以原来的为模板重新分配
32         newsh = s_realloc(sh, hdrlen+newlen+1);
33         if (newsh == NULL) return NULL;
34         s = (char*)newsh+hdrlen;
35     } else {
36         /* Since the header size changes, need to move the string forward,
37          * and can‘t use realloc */
38         newsh = s_malloc(hdrlen+newlen+1);
39         if (newsh == NULL) return NULL;
40         memcpy((char*)newsh+hdrlen, s, len+1);
41         s_free(sh);
42         s = (char*)newsh+hdrlen;
43         s[-1] = type;
44         sdssetlen(s, len);
45     }
46     sdssetalloc(s, newlen);
47     return s;
48 }
时间: 2024-08-06 11:41:30

redis源码分析1----字符串sds的相关文章

redis源码阅读——动态字符串sds

redis中动态字符串sds相关的文件为:sds.h与sds.c 一.数据结构 redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 1 typedef char *sds; 2 3 /* Note: sdshdr5 is never used, we just access the flags byte directly. 4 * However is here to document the layout of type 5 SDS strings. *

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

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

Redis源码学习:字符串

Redis源码学习:字符串 1.初识SDS 1.1 SDS定义 Redis定义了一个叫做sdshdr(SDS or simple dynamic string)的数据结构.SDS不仅用于 保存字符串,还用来当做缓冲区,例如AOF缓冲区或输入缓冲区等.如下所示,整数len和free分别表示buf数组中已使用的长度和剩余可用的长度,buf是一个原生C字符串,以\0结尾. sds就是sdshdr中char buf[]的别名,后面能看到,各种操作函数的入参和返回值都是sds而非sdshdr.那sdshd

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源码分析(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

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

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