深入了解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方式,有两种连接方式,一种是直接连接,一张是通过连接池连接 注意:以后我们都用的连接池方式连接,直 ...
随机推荐
- mui div滚动阻止触发下拉刷新
function orderListScroll () { var _orderObj = document.querySelector('.circulation-loan-list') //div ...
- web 部署专题(三):压力测试(一)工具 siege
1.介绍 Siege是一个压力测试和评测工具,设计用于WEB开发这评估应用在压力下的承受能力:可以根据配置对一个WEB站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访 ...
- day1 python计算器底层运作,注释及变量
每日一记 utf-8 国际标准编码(可变长的unicode编码)中文3字节,英文数字特殊字符1字节 gbk 中国标准编码 中文2字节,英文数字特殊字符1字节 1.原码,反码,补码 "&quo ...
- [Qt2D绘图]-05绘图设备-QPixmap&&QBitmap&&QImage&&QPicture
这篇笔记记录的是QPainterDevice(绘图设备,可以理解为一个画板) 大纲: 绘图设备相关的类:QPixmap QBitmap QImage QPicture QPixmap ...
- JAVA各种OOM代码样例及解决方法
周末了,觉得我还有很多作业没有写,针对目前大家对OOM的类型不太熟悉,那么我们来总结一下各种OOM出现的情况以及解决方法. 我们把各种OOM的情况列出来,然后逐一进行代码编写复现和提供解决方法. 1. ...
- 干货分享:Python Web 部署方式大全
不要让服务器裸奔 学过PHP的都了解,php的正式环境部署非常简单,改几个文件就OK,用FastCgi方式也是分分钟的事情.相比起来,Python在web应用上的部署就繁杂的多,主要是工具繁多,主流服 ...
- Linux/Docker 中使用 System.Drawing.Common 踩坑小计
前言 在项目迁移到 .net core 上面后,我们可以使用 System.Drawing.Common 组件来操作 Image,Bitmap 类型,实现生成验证码.二维码,图片操作等功能.Syste ...
- 题解 洛谷 P5163 【WD与地图】
首先将操作倒序,把删边转化为加边.先考虑若边是无向边,条件为连通,要怎么处理. 可以用并查集来维护连通性,对每个连通块维护一颗权值线段树,连通块的合并用线段树合并来实现,线段树同时也支持了修改点权. ...
- java计算下一个整5分钟时间点
需求背景 我的需求是获取当前时间之后的下一个"整5分钟时间点". 首先,那么何为"整5分钟时间点"? 满足以下两个条件的时间: 分钟数等于以下时间中的一个,且秒 ...
- 谁来教我渗透测试——VMware工具安装和使用
今天我们继续渗透测试学习系列了,昨天我们看了基础概念,今天我们来学习一下渗透测试必备的功能VMware安装. 首先我们下载好VMware workstation Pro的安装包.可以在百度上直接百度下 ...
。
。