「Redis」字符串
原文链接:https://www.changxuan.top/?p=1109
简介
Redis 中自定义的字符串结构。
字符串是 Redis 中最常用的一种数据类型,在 Redis 中专门封装了一个字符串结构体——简单动态字符串(Simple Dynamic String, SDS)。其结构体如下:
struct sdshdr {
// 记录 buf 数组中已使用字节的数量既 SDS 中所保存字符串的长度
int len;
// 记录 buf 数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串。
char buf[];
}
当 len 的值为 8 时,表示在 buf 数组中保存了一个 8 字节长的字符串;当 free 的值为 2 时,表示在 buf 数组中还有两个字节的空间未使用。如果为 0 ,则表示当前 buf 数组的空间已经全部分配完毕;buf 则是一个 char 类型的数组。SDS 遵循了C字符串以空字符结尾的惯例,即存储在 buf 中的字符串末尾都会紧跟一个空字符 \0 ,这个空字符对于用户来说是透明的,它并不会被计入 len 中。
优点
为什么要在 Redis 中要自定义字符串的数据结构?
1 时间复杂度
首先,由上面代码我们可以知道通过 SDS 获取字符串的长度的时间复杂度为 O(1)。而如果使用 C 字符串每次获取字符串长度时的时间复杂度则为 O(N)。即当我们使用 STRLEN 命令获取某个键值的长度时不用担心性能问题。
2 缓冲区溢出
其次,可以避免缓冲区溢出问题。例如,两个C字符串在内存中紧挨着,如果没有提前给前一个字符串分配足够空间的情况下就使用 strcat 函数在其末尾追加新的字符串。那么新拼接的字符串就会溢出到后一个字符串的空间中,从而导致后一个字符串的内容发生改变。但是在 SDS 中,对内容进行修改之前会先检查其内存空间是否满足要求,如果不满足要求,则会自动将空间扩展至所需要的大小。扩展空间大小的操作对于用户来说也是透明的。
另外,为了避免可能由于频繁的修改字符串内容,而导致产生较为耗时的内存重分配问题。SDS 通过以空间换时间的方式即未使用空间来尽量避免这种问题。在 SDS中实现了空间预分配和惰性空间释放两种优化策略。
优化策略
空间预分配
当 SDS 中的字符串变长时,程序先判断当前闲置空间是否满足需求。如果不满足,则按照空间预分配的策略对空间进行扩展。Redis 不仅仅只分配所需要的空间大小,则是根据规则多分配一些空间。当 SDS 修改后的新值长度小于 1MB(len 的长度)。那么程序将会分配和 len 同样大小的闲置空间,即 len = free 。buf 数组的实际长度则是 len + free + 1 字节。如果修改后的新值大于等于 1MB,程序则会分配 1MB 的未使用空间。
如此一来,就不需要每次增加字符串长度时必须对内存重新分配,从而提高了系统性能。
惰性空间释放
当 SDS 中的字符串变短时,程序并不是直接进行内存重分配回收多余的空间,而是使用 free 记录下来。如果将来再变长时,可以直接使用。
通过惰性空间释放,避免了缩短字符串时产生的内存重分配操作。
3 二进制安全
由于C字符串的特殊性,在一些场景中会出现问题。如,一个字符串中存在多个空字符,那么C字符串只能识别出第一个空字符之前的内容。且C字符串只能保存文本数据。
而 SDS 的 API 都是二进制安全的,所有的 API 都会以处理二进制的方式来处理 SDS 存放在 buf 数组中的数据,以保证数据写入前与读取后的一致性。
4 兼容部分C字符串函数
避免了重复造轮子的问题。
SDS API
| 函数 | 作用 | 备注 |
|---|---|---|
| sdsnew | 创建一个包含给定 C 字符串的 SDS | |
| sdsempty | 创建一个不包含任何内容的空 SDS | |
| sdsfree | 释放给定的 SDS | |
| sdslen | 返回 SDS 已使用的空间字节数 | |
| sdsavail | 返回SDS 未使用的空间字节数 | |
| sdsdump | 创建一个给定 SDS 的副本 | |
| sdsclear | 清空 SDS 保存的字符串内容 | |
| sdscat | 将给定的C字符串拼接到 SDS字符串末尾 | |
| sdscatsds | 将给定的SDS字符串拼接到另一个SDS字符串的末尾 | |
| sdscpy | 将给定的C字符串复制到 SDS中,并覆盖SDS中原有的字符串 | |
| sdsgrowzero | 用空字符将SDS扩展至给定长度 | |
| sdsrange | 保留SDS给定区间内的数据 | |
| sdstrim | 接受一个 SDS 和一个 C字符串作为参数,从 SDS 中移除所有在C字符串中出现过的字符 | |
| sdscmp | 对比两个 SDS 是否相同 |
「Redis」字符串的更多相关文章
- 【LOJ】#3095. 「SNOI2019」字符串
LOJ#3095. 「SNOI2019」字符串 如果两个串\(i,j\)比较\(i < j\),如果离\(a_{i}\)最近的不同的数是\(a_{k}\),如果\(j < k\)那么\(i ...
- 「JSOI2015」字符串树
「JSOI2015」字符串树 传送门 显然可以树上差分. 我们对于树上每一条从根出发的路径都开一 棵 \(\text{Trie}\) 树,那么我们就只需要在 \(\text{Trie}\) 树中插入一 ...
- 「Foundation」字符串
一.Foundation框架中一些常用的类 字符串型: NSString:不可变字符串 NSMutableString:可变字符串 集合型: 1)NSArray:OC不可变数组 NSMutableA ...
- 「Python」字符串操作内置函数
目录: capitalize casefold center count encode decode endswith expandtabs find format format_map index ...
- 分享一个网上搜不到的「Redis」实现「聊天回合制」的方案
前言 为什么说网上搜不到,因为关于聊天回合制的方案作者本人快把百度搜秃噜了也没找到,好在最终是公司一个关系不错的大佬帮提供了点思路,最终作者将其完整实现了出来. 分享出来大家可以收藏,万一你哪天也碰到 ...
- 「SNOI2019」字符串
题目 看起来非常一眼啊,我们完全可以\(std::sort\)来解决这歌问题 于是现在的问题转化成了比较函数怎么写 随便画一下就会发现前面的好几位是一样的,后面的好几位也是一样,只需要比较中间的一段子 ...
- 【LOJ】#2278. 「HAOI2017」字符串
题解 好神仙的题啊 感觉转二维平面能想到,算重复情况的方法真想不到啊 通过扒stdcall代码获得的题解QAQQQQ 我们先把\(p_i\)正串反串建出一个AC自动机来 然后我们把s串放在上面跑匹配, ...
- LOJ#2452. 「POI2010」反对称 Antisymmetry
题目描述 对于一个 \(0/1\) 字符串,如果将这个字符串 \(0\) 和 \(1\) 取反后,再将整个串反过来和原串一样,就称作「反对称」字符串.比如 \(00001111\) 和 \(01010 ...
- 「POI2010」反对称 Antisymmetry (manacher算法)
# 2452. 「POI2010」反对称 Antisymmetry [题目描述] 对于一个 $0/1$ 字符串,如果将这个字符串 $0$ 和 $1$ 取反后,再将整个串反过来和原串一样,就称作「反对称 ...
随机推荐
- <string name="xxx"> 的复杂用法:格式化及使用html标签
1.官方文档: https://developer.android.com/guide/topics/resources/string-resource 2.格式化字符串 2.1 示例 <res ...
- P2414 [NOI2011]阿狸的打字机 AC自动机
题意 给定n个模式串,有m个询问,每次询问第X个模式串在第Y个模中出现了多少次 解题思路 以fail树相反的方向建一棵树T,问题转化为X的子树中有多少个y的终止节点.跑出T的dfs序,X的子树就可以表 ...
- 2020重新出发,NOSQL,Redis主从复制
Redis主从复制 尽管 Redis 的性能很好,但是有时候依旧满足不了应用的需要,比如过多的用户进入主页,导致 Redis 被频繁访问,此时就存在大量的读操作. 对于一些热门网站的某个时刻(比如促销 ...
- vue、react等SPA应用页脚组件闪烁的解决办法
大家好,我是木瓜太香.大家在开发单页应用的时候,经常会遇到这样的需求,头部和尾部两个组件是大多数组件公用的,而中间的内容区域则是单独存在的,而且一般内容组件逻辑会比较多,如果我们不停刷新页面可能会出现 ...
- Activiti7 绑定业务主键以及流程定义 流程实例的挂起和激活
绑定业务主键businessKey /** * 绑定业务主键 */ @Test public void bindingBusinessKey() { // 获取RuntimeService Runti ...
- C016:字符串倒置
代码: #include "stdafx.h" #include <string.h> int _tmain(int argc, _TCHAR* argv[]) { c ...
- Java网络通信 —— 序列化问题
Java序列化的目的主要有两个: 1.网络传输 2.对象持久化 当选行远程跨迸程服务调用时,需要把被传输的Java对象编码为字节数组或者ByteBuffer对象.而当远程服务读取到ByteBuffer ...
- Scrapy框架的架构原理解析
爬虫框架--Scrapy 如果你对爬虫的基础知识有了一定了解的话,那么是时候该了解一下爬虫框架了.那么为什么要使用爬虫框架? 学习框架的根本是学习一种编程思想,而不应该仅仅局限于是如何使用它.从了解到 ...
- linux 多进程
Linux下的多进程编程初步 Linux下的多进程编程初步 多进程编程 1 Linux下进程的结构 2 Linux下的进程控制 21 僵尸进程 22 fork 23 exec 函数族 3 Linux下 ...
- [Java核心技术]五-继承(枚举类)
####Java枚举类型(enum) 枚举类型都是继承了Enum类(是一个抽象类)的一个类,我们可以向enum类中添加方法和变量等.编译再反编译可以看到枚举类型对应的类的内容. 每个枚举常量都对应一个 ...