Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾的字符数组,以下简称 C 字符串), 而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型, 并将 SDS 用作 Redis 的默认字符串表示。

  在 Redis 里面, C 字符串只会作为字符串字面量(string literal), 用在一些无须对字符串值进行修改的地方, 比如打印日志.

  当 Redis 需要的不仅仅是一个字符串字面量, 而是一个可以被修改的字符串值时, Redis 就会使用 SDS 来表示字符串值

1.SDS的定义

struct sdshdr {

    // 记录 buf 数组中已使用字节的数量, 不包括 '\0' 的长度
// 等于 SDS 所保存字符串的长度
int len; // 记录 buf 数组中未使用字节的数量
int free; // 字节数组,用于保存字符串
char buf[]; };

  buf [ ] 除了保存字符串的字符外, 还会在末尾保存一个空字符 '\0' , 空字符不计算在 len 属性之中.

  遵循空字符结尾的好处是可以重用一部分C字符串的函数.

2.SDS与C字符串的区别

2.1 常数复杂度获取字符串长度

  C字符串不记录自身的长度信息, 获取字符串长度时会遍历字节数组, 直到遇到空字符为止. 复杂度为 O(N)

  SDS直接通过 len 属性获取字符串长度. 复杂度为O(1)

2.2 杜绝缓冲区溢出

  C字符串不记录自身长度, 修改字符串时不会判断本身是否拥有足够的内存空间, 当内存空间不足时, 则会造成缓冲区的溢出.

  SDS对字符串进行修改时,先检查内存空间是否满足修改的需要, 若不满足, 则自动扩展SDS的内存空间. 所以使用SDS既不需要手动修改内存空间的大小, 也不会出现缓冲区溢出的情况.

2.3 空间预分配

  第一次创建字符串对象时, SDS不会分配冗余空间, 即 len = 0

  当SDS的API修改SDS时, 则会为其分配冗余空间.

  1. 当修改后的SDS的 len 属性小于1MB时, 则为其分配和 len 同样大小的冗余空间, 即 free = len, 此时 buf [ ] 的实际长度 = len(实际长度) + free(冗余空间) + 1(空字符)
  2. 当修改后的SDS的 len 属性大于等于1MB时, 则为其分配1MB的冗余空间.  buf [ ] 的实际长度 = len(实际长度) + free(1MB) + 1(空字符)

2.4 惰性空间释放

  SDS的API缩短SDS的字符串时, 不会立即使用内存分配回收缩短后多出来的字节, 而是记录在 free 属性中, 并等待将来使用.

2.5 二进制安全

  C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。

  SDS的API都是二进制安全的.所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设,数据在写入时是什么样的,它被读 取时就是什么样。

  这也是我们将SDS的buf属性称为字节数组的原因——Redis不是用这个数组来保存字符,而是用它来保存一系列二进制数据。

3. 字符串的不同编码方式

  传送门 : Redis 数据编码方式详解

  为什么会有不同的编码方式,为了解释这种现象,我们首先来了解一下 Redis 对象头结构体,所有的 Redis 对象都有下面的这个结构头:

struct RedisObject {
int4 type; // 4 bits
int4 encoding; // 4 bits
int24 lru; // 24 bits
int32 refcount; // 4 bytes
void *ptr; // 8 bytes,64-bit system
} robj;

  不同的对象具有不同的类型 type(4bit),同一个类型的 type 会有不同的存储形式 encoding(4bit),为了记录对象的 LRU 信息,使用了 24 个 bit 来记录 LRU 信息。

  每个对象都有个引用计数,当引用计数为零时,对象就会被销毁,内存被回收。ptr 指针将指向对象内容 (body) 的具体存储位置。

  这样一个 RedisObject 对象头需要占据 16 字节的存储空间。

3.1 embstr

  从Redis 3.0版本开始字符串引入了EMBSTR编码方式,长度小于OBJ_ENCODING_EMBSTR_SIZE_LIMIT(39)的字符串将以EMBSTR方式存储。

  EMBSTR方式的意思是 embedded string ,字符串的空间将会和redisObject对象的空间一起分配,两者在同一个内存块中。

  Redis中内存分配使用的是jemalloc,jemalloc分配内存的时候是按照 8,16,32,64 作为chunk的单位进行分配的。

  为了保证采用这种编码方式的字符串能被jemalloc分配在同一个chunk中,该字符串长度不能超过64,

  故字符串长度限制OBJ_ENCODING_EMBSTR_SIZE_LIMIT = 64 - sizeof('0') - sizeof(robj)为16 - sizeof(struct sdshdr)为8 = 39。

  采用这个方式可以减少内存分配的次数,提高内存分配的效率,降低内存碎片率。

3.2 raw

  从len字段可以判断并不不依赖于'0',故可以用与保存二进制对象。

  从free字段可以判断其空间分配是采用预分配的方式,避免字符串修改时频繁分配释放内存。

3.3 int

  INT编码方式以整数保存字符串数据,仅限能用long类型值表达的字符串。

  当robj中的LRU值没有意义的时候(实例没有设置maxmemory限制或者maxmemory-policy设置的淘汰算法中不计算LRU值时),

  0-10000之间的OBJ_ENCODING_INT编码的字符串对象将进行共享。

Redis---SDS(简单动态字符串)的更多相关文章

  1. Redis的简单动态字符串实现

    Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾的字符数组,以下简称 C 字符串), 而是自己构建了一种名为简单动态字符串(simple dynamic string,sds)的抽象类 ...

  2. 小白的Redis学习(一)-SDS简单动态字符串

    本文为读<Redis设计与实现>的记录.该书以Redis2.9讲解Redis相关内容.请注意版本差异. Redis使用C语言实现,他对C语言中的char类型数据进行封装,构建了一种简单动态 ...

  3. Redis数据类型之SDS简单动态字符串

    一,简单的动态字符串 1,Redis自己构建了一种名为简单动态字符串的抽象类型,并将SDS用作Redis的默认字符串表示, 2,在redis的数据库里面,包含字符串值的键值对在底层都是由SDS实现的 ...

  4. 深入理解Redis 数据结构—简单动态字符串sds

    Redis是用ANSI C语言编写的,它是一个高性能的key-value数据库,它可以作用在数据库.缓存和消息中间件.其中 Redis 键值对中的键都是 string 类型,而键值对中的值也是有 st ...

  5. 【Redis】简单动态字符串SDS

    C语言字符串 char *str = "redis"; // 可以不显式的添加\0,由编译器添加 char *str = "redis\0"; // 也可以添加 ...

  6. 深入理解Redis之简单动态字符串

    目录 SDS SDS与C字符串的区别 SDS获取字符串长度复杂度为O(1),C字符串为O(N) SDS杜绝了缓存区溢出 减少修改字符串时带来的内存重分配次数 二进制安全 Redis没有直接使用C语言传 ...

  7. 关于redis中SDS简单动态字符串

    1.SDS 定义 在C语言中,字符串是以’\0’字符结尾(NULL结束符)的字符数组来存储的,通常表达为字符指针的形式(char *).它不允许字节0出现在字符串中间,因此,它不能用来存储任意的二进制 ...

  8. redis之简单动态字符串(SDS)

    O(N):时间复杂度 N的值越大 时间复杂度随N的平方增大 O(1):就是说N很大的时候,复杂度基本不增长了.基本就是常量了. 在Redis数据库里 包含字符串值的键值对 在底层都是由SDS实现的. ...

  9. sds(简单动态字符串) 内存预分配优化策略

    * 1024 , 也就是说. 当大小小于 1MB 的字符串运行追加操作时,sdsMakeRoomFor 就为它们分配多于所需大小一倍的空间: 当字符串的大小大于 1MB . 那么 sdsMakeRoo ...

  10. Redis数据结构之简单动态字符串SDS

    Redis的底层数据结构非常多,其中包括SDS.ZipList.SkipList.LinkedList.HashTable.Intset等.如果你对Redis的理解还只停留在get.set的水平的话, ...

随机推荐

  1. Eclipse的SVN插件使用

    1       eclipse的SVN插件使用 1.1     svn插件安装 下载Subversion的eclipse插件 http://subclipse.tigris.org/servlets/ ...

  2. 2019.01.21 bzoj2989: 数列(二进制分组+主席树)

    传送门 二进制分组入门题. 主席树写错调题2h+2h+2h+体验极差. 题意简述:给一堆点,支持加入一个点,询问有多少个点跟(x,y)(x,y)(x,y)曼哈顿距离不超过kkk. 思路:题目要求的是对 ...

  3. 2019.01.14 bzoj2648: SJY摆棋子(kd-tree)

    传送门 kd−treekd-treekd−tree模板题. 题意简述:支持在平面上插入一个点,求对于一个点的最近点对. 思路:cdqcdqcdq是一种很不错的分治方法 只是好像码量有点窒息 所以我用了 ...

  4. SQL语句关联查询

    一:连接类型: 关联查询:只有存在关联的表才能关联查询,完全独立的表之间无法关联 1.关联的类型:自关联,左关联,右关联,全关联(full join)两张表都是主表 2.关联的表:两张以上,以一张(或 ...

  5. c# devexpress 多个窗口

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  6. 总结一下《vue的使用》

    1.用vue创建项目的时候, 1.安装axios,对axios进行处理,创建axios.js文件,设置基础请求地址, 设置前置守卫和独享守卫,对请求数据进行设置,(特别实在进行token验证的时候特别 ...

  7. SVN被锁定的几种解决方法

    用SVN经常出现被锁定而无法提交的问题,选择解锁又提示没有文件被锁定,很是头疼.这里整理了一下SVN被锁定的几种解决方法: 1.出现这个问题后使用“清理”即"Clean up"功能 ...

  8. 第19章:MongoDB-聚合操作--聚合管道--$limit+$skip

    ①$limit “$limit”:负责数据的取出个数: “$skip”:数据的跨过个数. ②范例:使用“$limit”设置取出的个数

  9. matlab画无向图,基于坐标的无向图联系作者

    %函数名netplot %使用方法输入请help netplot %无返回值 %函数只能处理无向图 %作者:tiandsp %最后修改: function netplot(A,flag) %调用方法输 ...

  10. redis的repl-ping-slave-period和repl-ping-replica-period

    网上很多Redis方面的文章,会涉及到repl-ping-slave-period和repl-ping-replica-period这两个重要参数,从一些中文解释来看,意思差不多,即:SLAVE周期性 ...