Redis设计与实现一之简单的动态字符串
简单的动态字符串
Redis没有直接使用C语言中的字符串,而是自己构建了SDS这样的一种简单动态字符串,并且将他作为Redis中字符串的默认的表示。
但是并未完全抛弃C语言字符串,只不过是在C语言字符串的基础上,通过封装其他的属性,构造出一个更加高效的字符串的封装结构。
SDS : redis 没有直接使用C语言的字符串,构建了一种简单动态字符串(simple dynamic string,即SDS)的抽象类型作为redis的默认字符串
SDS 用来保存字符串外,还被用作缓冲区,如:AOF缓存区,客户端输入缓冲区
C语言字符串 vs SDS
| 对比项 | C语言字符串 | SDS |
|---|---|---|
| 获取字符串长度的复杂度 | O(N) | O(1) |
| API安全性 | 不安全,会造成缓冲区溢出 | 安全 |
| 修改字符串长度 | 修改N次,必须执行N次内存重分配 | 修改N次,最多需要执行N次内存重分配 |
| 保存数据格式类型 | 只能保存文本数据 | 文本或二进制数据 |
| 函数库<string.h>使用度 | 全部可用 | 只有部分可用 |
SDS的定义
SDS示例:
1. free属性的值0,该SDS没有空闲的未使用空间。
2. len 为5,表示字符串的长度为5
3. buf是一个为CAHR字符数组,最后一位为\0标识字符串的结束。
SDS遵循C字符串以空字符结束的惯例,保留空字符的1字节的空间,不计算在SDS的len属性中,并且为空字符分配一个字节的空间,遵循这一惯例使得SDS仍然可以使用部分C语言字符串的一些函数。
SDS与C字符串的区别
Redis对字符串在安全性、效率和功能方面都要求很高,如果直接照搬C语言的字符串,会出现很多问题,而且当获取长度等操作的时候,效率明显很低达到O(N)。
获取字符串长度
1. C字符串不记录自己的长度,当需要获取长度的时候,需要遍历整个字符数组,对遇到的所有的字符进行计数,直到遇到空字符为止,执行时间复杂度为O(N)。
2. SDS字符串保存了自身的长度,当需要获取长度的时候,直接可以获取到,这样把时间复杂度降低到O(1)。图中为2.9版本中实例,如果3.2这里的free是没有的,计算free需要用alloc-len算出。
杜绝缓冲区溢出
C语言不记录字符串长度的另一个弊端就是容易造成缓冲区溢出。举个例子,拼接两个字符串:wy和xx
假设前者为s1后者为s2,那么这里在进行字符串拼接的时候,一旦没有为s1从新分配适合的空间的话,那么拼接后的结果会溢出到s2的空间中去。
SDS在进行字符串拼接的时候,会自行检查是不是内存空间是不是 满足要求,如果不满足的话,自动进行分配,而且在进行分配空间的时候,会实行预先分配的策略。
减少修改字符串时带来的内存重分配次数
C语言字符串在进行字符串的扩充和收缩的时候,都会面临着内存空间的重新分配问题。
1. 字符串拼接会产生字符串的内存空间的扩充,在拼接的过程中,原来的字符串的大小很可能小于拼接后的字符串的大小,那么这样的话,就会导致一旦忘记申请分配空间,就会导致内存的溢出。
2. 字符串在进行收缩的时候,内存空间会相应的收缩,而如果在进行字符串的切割的时候,没有对内存的空间进行一个重新分配,那么这部分多出来的空间就成为了内存泄露。
Redis在内存空间分配的问题上进行了优化,主要分为两个过程。
1. 内存预分配
内存空间进行分配的时候,预先分配一块多余的空间给当前的字符串对象,使得,在下一次字符串比如拼接的时候,尽可能保证其内存空间的足够用,不需要再去分配内存,这样的话,效率将会大大的提升。
额外分配未使用空间数量:
1). 如果修改之后SDS的长度小于1MB,那么程序将会分配和当前字符串len相同的空间给该字符串对象。比如说,wy和xx进行合并,这里是4个字符,大小为4,实际的存储大小为5,但是分配的内存空间大小会采用预分配的方式,那么分配后的内存大小为4+4+1=9个字节。
2). 如果修改之后的SDS的长度大小大于等于1MB的话,程序分配的内存空间将会为1MB,比如说变化后的字符串对象达到了30M,当他在分配空间的时候只分配1MB空间。那么最终空间大小为30M+1MB+1B.
2. 惰性释放
当字符串进行缩短操作的时候,并不立即将空间释放出来,而是,将这部分空间通过free进行标识,本字符串有多少的空余的空间。这样的话,在再次使用时也可以避免分配内存造成的时间开销。
当然,Redis中提供了专门的API,需要的时候,会真正的释放这部分空闲的内存。
二进制安全
C字符串必须符合某种编码,除了字符串的末尾外,不能包含空字符,否则会被误认为是字符串的结尾,导致最终读取的字符串是不完整的。
这些限制导致了字符串不能用于存放图片、音频、视频等二进制数据,只能存放文本数据。
但是在Redis中,不是靠空字符来判断字符串的结束的,而是通过len这个属性。那么,即便是中间出现了空字符对于SDS来说,读取该字符仍然是可以的。
总结:
- redis 3.2之后,针对不同长度的字符串引入了不同的SDS数据结构,并且强制内存对齐1,将内存对齐交给统一的内存分配函数,从而达到节省内存的目的
- SDS的字符串长度通过
sds->len来控制,不受限于C语言字符串\0,可以存储二进制数据,并且将获取字符串长度的时间复杂度降到了O(1) - SDS的头和
buf字节数组的内存是连续的,可以通过寻址方式获取SDS的指针以及flags值 - SDS的拼接扩展有一个内存预分配策略,用空间减少每次拼接的内存重分配可能性
- SDS的缩短并不会真正释放掉对应空闲空间
- SDS分配内存都会多分配1个字节用来在
buf的末尾追加一个\0,在部分场景下可以和C语言字符串保证同样的行为甚至复用部分string.h的函数
源码可以查看《Redis设计与实现》书籍
Redis设计与实现一之简单的动态字符串的更多相关文章
- Redis设计与实现读书笔记——简单动态字符串
前言 项目里用到了redis数据结构,不想只是简单的调用api,这里对我的读书笔记做一下记录.原文地址: http://www.redisbook.com/en/latest/internal-dat ...
- redis系列之------简单的动态字符串(SDS)
前言 Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾的字符数组,以下简称 C 字符串), 而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的 ...
- Redis 设计与实现 6:五大数据类型之字符串
前文 Redis 设计与实现 2:Redis 对象 说到,五大数据类型都会封装成 RedisObject. typedef struct redisObject { unsigned type:4; ...
- 【笔记】《Redis设计与实现》chapter2 简单动态字符串
------------恢复内容开始------------ 2.1 SDS的定义 struct sdshdr{ // 记录buf数组中已使用字节的数量 // 等于SDS所保存字符串的长度(不含'\0 ...
- redis设计与实现(一)简单动态字符串
redis是C语言实现的,但redis中的字符串并没有直接用C语言中的字符串表示,而是自己构建了一种简单的动态字符串类型(SDS). 在redis里面,C字符串只用作字面量,用在一些不会修改的地方,e ...
- 探索Redis设计与实现1:Redis 的基础数据结构概览
本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...
- Redis设计与实现
简述Redis设计与实现 Redis是一个高性能的key-value的非关系型数据库,Redis是运行在内存中的一种数据库,但是它也可以持久化到磁盘中,Redis的实现有着更为复杂的数据结构并且提供对 ...
- Redis数据类型之SDS简单动态字符串
一,简单的动态字符串 1,Redis自己构建了一种名为简单动态字符串的抽象类型,并将SDS用作Redis的默认字符串表示, 2,在redis的数据库里面,包含字符串值的键值对在底层都是由SDS实现的 ...
- 180626-Spring之借助Redis设计一个简单访问计数器
文章链接:https://liuyueyi.github.io/hexblog/2018/06/26/180626-Spring之借助Redis设计一个简单访问计数器/ Spring之借助Redis设 ...
随机推荐
- APP UI自动化测试思路总结
python+appium自动化测试系列就要告一段落了,本篇博客咱们做个小结. 首先想要说明一下,APP自动化测试可能很多公司不用,但也是大部分自动化测试工程师.高级测试工程师岗位招聘信息上要求的,所 ...
- 【总结】sqlserver
1 基础 1.1 简介 sqlserver是闭源的,必须运行在windows平台上的数据库.默认事务隔离级别是读已提交(commit read).全称Microsoft SQL Server,说以也被 ...
- 基于虚拟机安装Linux并利用LVM创建磁盘分区
主要步骤:将磁盘设置为LVM类型,并在这四个分区上创建物理卷.卷组和逻辑卷,最后将逻辑卷挂载.电脑使用Windows10 企业版 LTSC 操作系统.虚拟机使用VMware15.5 pro . 1.安 ...
- Codeforces 1404 D. Game of Pairs
Codeforces 1404 D.Game of Pairs 给定\(2n\)个数\(1,2,...,2n\),A 和 B 将进行交互,规则如下: A 需要将元素分成 n 组 \(\mathbf{p ...
- C# OpenFileDialog和SaveFileDialog的常见用法
#region 示例1 SaveFileDialog sfd = new SaveFileDialog(); //设置文件类型 sfd.Filter = "备份文件(*.bak)|*.bak ...
- spring-cloud-config 多服务共享公共配置的解决方案总结,太全了!
问题描述 我们公司的项目是基于SpringCloud开发的微服务,用到了Spring-Cloud-Config作为微服务统一的配置中心,可以将散落在各个服务的配置进行统一配置管理. 虽然配置中心将各个 ...
- tcpack---1简述
TCP重传机制 TCP要保证所有的数据包都可以到达,所以,必需要有重传机制. 超时重传机制 一种是不回ack,死等3,当发送方发现收不到3的ack超时后,会重传3.一旦接收方收到3后,会ack 回 4 ...
- Best Time to Buy and Sell Stock I II III IV
一.Best Time to Buy and Sell Stock I Say you have an array for which the ith element is the price of ...
- vue 使用中的小技巧 (一)
在vue的使用过程中会遇到各种场景,当普通使用时觉得没什么,但是或许优化一下可以更高效更优美的进行开发.下面有一些我在日常开发的时候用到的小技巧 data 和 Object.freeze 每个Vue实 ...
- rpm命令介绍
rpm安装不能指定安装位置. 查看系统安装了哪些软件:rpm -qa rpm -qa |grep keyword (q:query a 是all) 查看软件是否安装: rpm -q ...