深入了解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方式,有两种连接方式,一种是直接连接,一张是通过连接池连接 注意:以后我们都用的连接池方式连接,直 ...
随机推荐
- APP开发---后台设计
想了好久才准备写博客的,希望能对我自己和大家都有帮助! 话不多说,直接正题! ------------------------------------------------------------- ...
- sqlilabs 1-20关 payload
1.联合查询注入:http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,user(),3 --+http://127.0.0.1/sqli/Less- ...
- ES2020的这些新功能令人期待
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文出处:https://blog.bitsrc.io/es2020-has-been-finalized- ...
- 介绍下重绘和回流(Repaint & Reflow),以及如何进行优化
1. 浏览器渲染机制 浏览器采用流式布局模型(Flow Based Layout) 浏览器会把HTML解析成DOM,把CSS解析成CSSOM,DOM和CSSOM合并就产生了渲染树(Render Tre ...
- 题解:2018级算法第六次上机 C6-危机合约
题目描述 样例: 实现解释: 没想到你也是个刀客塔之二维DP 知识点: 动态规划,多条流水线调度?可以看做一种流水线调度 坑点: 输入内容的调整(*的特殊判定),开头结尾的调整策略 从题意可知,要做的 ...
- hacknos靶机实战
工具: kali 192.168.1.6 nmap 打开使用nmap -sP 192.168.1.0/24 扫描活跃的主机 发现目标ip 使用nmap 查看开启了什么服务Nmap -v -A -PN ...
- MCU 51-5中断
中断概念 : 计算机执行某程序时,发生了紧急事件或有特殊请求,CPU暂停某程序的执行, 转而去处理上述事件或请求,处理完毕后再重新执行某程序的过程叫做中断. 数据的输入/输出传送方式: (1)无条件传 ...
- python技巧 namedtuple
python的namedtuple可以创建一个带字段名的元祖和一个带名字的类 In [1]: from collections import namedtuple ...: ...: nginx=na ...
- hihoCoder 1052 基因工程 最详细的解题报告
题目来源:基因工程 解题思路:假设基因序列长度为N,则需要计算基因序列前K个和后K个相同所需要的最少改变次数sum. 假设基因序列为 ATACGTCT (即M=8),K=6:interval=M-K= ...
- python利用difflib判断两个字符串的相似度
我们再工作中可能会遇到需要判断两个字符串有多少相似度的情况(比如抓取页面内容存入数据库,如果相似度大于70%则判定为同一片文章,则不录入数据库) 那这个时候,我们应该怎么判断呢? 不要着急,pytho ...
。
。