深入了解Redis(1)-字符串底层实现
一.简单动态字符串(SDS)
Redis中字符串实现有两种方式,C语言传统字符串(以空字符结尾的字符数组)和简单动态字符串(SDS),并将SDS作为默认字符串表示.
C字符串只会作为字符串字面量,用在一些无需对字符串值进行修改的地方,比如打印日志:
redisLog(REDIS_WARNING,"Redis is now ready to exit, bye bye...");
二.SDS的实现
每个 sds.h/sdshdr 结构表示一个SDS值:
struct sdshdr {
// 记录 buf 数组中已使用字节的数量
// 等于 SDS 所保存字符串的长度
int len;
// 记录 buf 数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
};
图 2-1 展示了一个 SDS 示例:
free属性的值为0, 表示这个 SDS 没有分配任何未使用空间。len属性的值为5, 表示这个 SDS 保存了一个五字节长的字符串。buf属性是一个char类型的数组, 数组的前五个字节分别保存了'R'、'e'、'd'、'i'、's'五个字符, 而最后一个字节则保存了空字符'\0'。

SDS 遵循 C 字符串以空字符结尾的惯例, 保存空字符的 1 字节空间不计算在 SDS 的 len 属性里面, 并且为空字符分配额外的 1 字节空间, 以及添加空字符到字符串末尾等操作都是由 SDS 函数自动完成的, 所以这个空字符对于 SDS 的使用者来说是完全透明的。
遵循空字符结尾这一惯例的好处是, SDS 可以直接重用一部分 C 字符串函数库里面的函数。
三.SDS与C字符串的区别
c字符串是由长度为n+1的字符串数组实现的,并且数组的最后一个值总是为空字符'\0'.
比如说, 图 2-3 就展示了一个值为 "Redis" 的 C 字符串:

1.SDS可以更快的获取字符串长度
因为c字符串的数据结构为数组,所以也继承了数组的基本特性,比如获取该字符串的长度,需要去遍历整个数组,该操作的复杂度为O(N).
而SDS本身就维护了len属性记录了字符串的长度,所以获取SDS字符串长度的操作复杂度为O(1).
2.杜绝缓冲区溢出
c字符串容易造成缓冲区溢出,因为c字符串本身不记录自身长度,比如<string.h>/strcat 函数拼接字符串时,可能导致内存空间不足.
SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性,当 SDS API 需要对 SDS 进行修改时,API 会先检查 SDS 的空间是否满足修改所需的要求,如果不满足的话,API 会自动将 SDS 的空间扩展至执行修改所需的大, 然后才执行实际的修改操作,所以使用 SDS 既不需要手动修改 SDS 的空间大小,也不会出现前面所说的缓冲区溢出问题.
3.减少修改字符串时带来的内存重分配次数
因为 C 字符串并不记录自身的长, 所以对于一个包含了 N 个字符的 C 字符串来说,这个 C 字符串的底层实现总是一个 N+1 个字符长的数组.在对字符串的增长或者缩短操作中,很容易造出内存溢出和内存泄漏.
为了避免 C 字符串的这种缺陷,SDS 通过未使用空间解除了字符串长度和底层数组长度之间的关联:在 SDS 中,buf 数组的长度不一定就是字符数量加一,数组里面可以包含未使用的字节,而这些字节的数量就由 SDS 的 free 属性记录.通过未使用空间,SDS 实现了空间预分配和惰性空间释放两种优化策.
4.空间预分配
空间预分配用于优化 SDS 的字符串增长操作:当 SDS 的 API 对一个 SDS 进行修改,并且需要对 SDS 进行空间扩展的时候,程序不仅会为 SDS 分配修改所必须要的空间,还会为 SDS 分配额外的未使用空间.
5.惰性空间释放
惰性空间释放用于优化 SDS 的字符串缩短操作:当 SDS 的 API 需要缩短 SDS 保存的字符串, 程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用 free 属性将这些字节的数量记录起, 并等待将来使用.
6.二进制安全
C 字符串中的字符必须符合某种编码(比如 ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 C 字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据.
为了确保 Redis 可以适用于各种不同的使用场景,SDS 的 API 都是二进制安全的(binary-safe): 所有 SDS API 都会以处理二进制的方式来处理 SDS 存放在 buf 数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设 —— 数据在写入时是什么样的,它被读取时就是什么.这也是我们将 SDS 的 buf 属性称为字节数组的原因 —— Redis 不是用这个数组来保存字符, 而是用它来保存一系列二进制数据.比如, 使用 SDS 来保存之前提到的特殊数据格式就没有任何问题,因为 SDS 使用 len 属性的值而不是空字符来判断字符串是否结束.
7.兼容部分c字符串函数
虽然 SDS 的 API 都是二进制安全的,但它们一样遵循 C 字符串以空字符结尾的惯例:这些 API 总会将 SDS 保存的数据的末尾设置为空字符,并且总会在为 buf 数组分配空间时多分配一个字节来容纳这个空字符,这是为了让那些保存文本数据的 SDS 可以重用一部分 <string.h>库定义的函数.
总结区别:
| C 字符串 | SDS |
|---|---|
获取字符串长度的复杂度为 。 |
获取字符串长度的复杂度为 。 |
| API 是不安全的,可能会造成缓冲区溢出。 | API 是安全的,不会造成缓冲区溢出。 |
修改字符串长度 N 次必然需要执行 N 次内存重分配。 |
修改字符串长度 N 次最多需要执行 N 次内存重分配。 |
| 只能保存文本数据。 | 可以保存文本或者二进制数据。 |
可以使用所有 <string.h> 库中的函数。 |
可以使用一部分 <string.h> 库中的函数。 |
深入了解Redis(1)-字符串底层实现的更多相关文章
- Redis的字符串底层是啥?为了速度和安全做了啥?
面试场景 面试官:Redis有哪些数据类型? 我:String,List,set,zset,hash 面试官:没了? 我:哦哦哦,还有HyperLogLog,bitMap,GeoHash,BloomF ...
- Redis 数据结构-字符串源码分析
相关文章 Redis 初探-安装与使用 Redis常用指令 本文将从以下几个部分进行介绍 1.前言 2.常用命令 3.字符串结构 4.字符串实现 5.命令是如果操作字符串的 前言 平时在使用 Redi ...
- redis之字符串命令源代码解析(二)
形象化设计模式实战 HELLO!架构 redis命令源代码解析 在redis之字符串命令源代码解析(一)中讲了get的简单实现,并没有对 ...
- 高性能的Redis之对象底层实现原理详解
对象 在前面的数个章节里, 我们陆续介绍了 Redis 用到的所有主要数据结构, 比如简单动态字符串(SDS).双端链表.字典.压缩列表.整数集合, 等等. Redis 并没有直接使用这些数据结构来实 ...
- Redis操作字符串工具类封装,Redis工具类封装
Redis操作字符串工具类封装,Redis工具类封装 >>>>>>>>>>>>>>>>>>& ...
- redis数据类型-字符串类型
Redis数据类型 字符串类型 字符串类型是Redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据.你可以用其存储用户的邮箱.JSON化的对象甚至是一张图片.一个字符串类型键允许存储的 ...
- 【Redis面试题】Redis的字符串是怎么实现的?
年前本人在找工作面试时在Redis相关问题上可栽了跟头.在面试前按常规套路准备了一下,比如 Redis 的常用5种数据结构,Redis持久化策略,Redis实现分布式锁,简单发布订阅等等都准备了,当时 ...
- Redis 操作字符串数据
Redis 操作字符串数据: > set name "Tom" // set 用于添加 key/value 数据,如果 key 存在则覆盖 OK > setnx nam ...
- 第二百九十五节,python操作redis缓存-字符串类型
python操作redis缓存-字符串类型 首先要安装redis-py模块 python连接redis方式,有两种连接方式,一种是直接连接,一张是通过连接池连接 注意:以后我们都用的连接池方式连接,直 ...
随机推荐
- SQLserver-MySQL的区别和用法
对于程序开发人员而言,目前使用最流行的两种后台数据库即为MySQL and SQL Server.这两者最基本的相似之处在于数据存储和属于查询系统.你可以使用SQL来访问这两种数据库的数据,因为它们都 ...
- 数据可视化之DAX篇(十三)熟练使用FORMAT函数,轻松自定义数据格式
https://zhuanlan.zhihu.com/p/64420449 在进行数据分析时,需要对某个数据进行格式调整的情形经常会遇到,在DAX中有一个专门进行格式调整的函数:FORMAT. 其实对 ...
- python 面向对象专题(十一):特殊方法 (四)__get__、__set__、__delete__ 描述符(四)描述符用法建议
使用特性以保持简单 内置的 property 类创建的其实是覆盖型描述符,__set__ 方法和__get__ 方法都实现了,即便不定义设值方法也是如此. 特性的__set__ 方法默认抛出 Attr ...
- 数据可视化之分析篇(六)使用Power BI进行流失客户分析
https://zhuanlan.zhihu.com/p/73358029 为了提升销量,在不断吸引新客户的同时,还要防止老客户离你而去,但每一个顾客不可能永远是你的客户,不可避免的都会经历新客户.活 ...
- 浏览器常见攻击方式(XSS和CSRF)
常见的浏览器攻击分为两种,一种为XSS(跨站脚本攻击),另一种则为CSRF(跨站请求伪造). XSS(跨站脚本攻击) 定义 XSS 全称是 Cross Site Scripting,为了与“CSS”区 ...
- Java多线程原理+基础知识(超级超级详细)+(并发与并行)+(进程与线程)1
Java多线程 我们先来了解两个概念!!!! 1.什么是并发与并行 2.什么是进程与线程 1.什么是并发与并行 1.1并行:两个事情在同一时刻发生 1.2并发:两个事情在同一时间段内发生 并发与并行的 ...
- RAC环境上搭建DG
首先RAC要确实是开归档的状态archive log list;如果是非归档状态,需要执行下面几步srvctl stop database -d +数据库实例名 关闭数据库--节点1(要做DG主库的) ...
- tensorflow对鸢尾花进行分类——人工智能入门篇
tensorflow之对鸢尾花进行分类 任务目标 对鸢尾花数据集分析 建立鸢尾花的模型 利用模型预测鸢尾花的类别 环境搭建 pycharm编辑器搭建python3.* 第三方库 tensorflow1 ...
- STL源码剖析:迭代器
准备知识 什么是迭代器? 迭代器是链接容器和算法的桥梁,所有的算法都通过迭代器操作容器中的数据 迭代器是一种智能指针,最重要的操作符重载就是operator*,operator-> 迭代器的实现 ...
- Java继承多态
一. 父类引用指向子类对象父类名(接口名) 对象名=new 子类(接口)名称() 二. 父子继承关系中,成员变量重名1.直接通过子类对象访问成员变量 =左边是谁,就优先用谁2.间接通过成员方法访问成员 ...
。
。