Redis-简单动态字符串(SDS)

文章目录

    • 文章概要
    • SDS数据结构定义
    • SDS和C字符串的区别
    • 总结
    • 参考

文章概要

本篇文章,我们来学习Redis字符串的编码格式SDS编码,文章将将从以下几个方面介绍SDS:

  • SDS的底层数据结构定义
  • Redis是C写的,那SDS和C中的字符串的区别是什么

SDS数据结构定义

//redis/deps/sds.h
/* Note: sdshdr5 is never used, we just access the flags byte directly.* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) hisdshdr5 {unsigned char flags; /* 3 lsb of type, and 5 msb of string length */char buf[];
};
struct __attribute__ ((__packed__)) hisdshdr8 {uint8_t len; /* used */uint8_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) hisdshdr16 {uint16_t len; /* used */uint16_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) hisdshdr32 {uint32_t len; /* used */uint32_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) hisdshdr64 {uint64_t len; /* used */uint64_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};

以上代码来自Redis的源码,其中包含了redis关于sds字符串编码的定义。从源码中可以看出,redis定义了5种sds的编码,但是hisdshdr5 被声明是没有被使用的,它的定义个后面的四种也略显区别。根据注释的描述,该结构只是为了展示 5 型sds的布局结构,flags的低3位表示类型,高5位表示字符串的长度。

重点看后面的几种定义。

各个字段的含义

  • len 表示实际字符串的长度,比如我们存入一个"HELLO",len = 5
  • alloc 表示实际分配给buf的内存空间大小,但是不包括头和空结束符
  • flags 只使用低三位作为有效位,表示sds类型(因为有四种,所以要三位,0不算),高5位未使用
  • buf 存储字符串的字节数组(这里称之为字节数据,而不是字符数组)

在这里插入图片描述

SDS和C字符串的区别

常数时间复杂度获取字符串的长度
这一点是显而易见的,因为在结构体的内部维护了一个变量len来记录实际字符串的长度,而在C语言中, 我们不得不遍历字符串才能获取字符串的长度。

杜绝缓冲区溢出
首先在C语言中,我们如果想在一个字符串后面追加字符串时,如果预先知道该区域还有多少可用的空间,将很容易发生缓冲区溢出的错误。
举个例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char **argv)
{int size = sizeof(char) * 7;char *dest = (char*)malloc(size);memset(dest, 0, size);const char *src = "HelloWorld";// 此时将发生缓冲区溢出,因为追加的字符串超出了dest所申请的内存char *result = strcat(dest, src);free(dest);return 0;
}

还有一种情况,假设一个字符串s1和另一个字符串s2相邻存储的,当我们向s1后面追加字符串时,将意外地修改s2的值。
对于SDS存储的字符串,当需要修改值时,相关的API会先检查该SDS所分配的空间是否满足此次修改,如果不满足,将自动执行扩容操作。所以对于SDS存储的字符串,杜绝了缓冲区溢出的可能性。

减少字符串修改时内存重分配的次数

在C语言中,字符串底层实际上是一个字符数组,数组的长度为字符串的长度+1,当对该字符串进行追加和缩减的时候,程序都要对其进行重新分配内存。内存分配涉及系统调用,会发生用户态和内核态的切换,如果大量的这种操作将严重影响程序的性能。
在Redis中,为了减少内存分配的次数,采用了两种内存空间分配的优化策略:

  • 空间预分配
  • 空间惰性删除

空间预分配技术用于优化字符串的增长操作,当程序为字符串分配空间时,不仅会为它分配字符串大小的空间,而且会分配一些未使用的空间,策略如下:

  • 如果修改后字符串的大小小于1MB,则此时未使用的空间和len的大小相等。也就是多分配一倍。
  • 和上面相对应的,如果修改后字符串的大小大于1MB,此时程序会为该SDS分配1MB的未使用空间。

举个例子:

  1. 假设我们要存储一个字符串 “Redis”,此时将分配 11 Bytes = 5(len) + 5(free) + 1(null terminator)的空间
  2. 假设我们要存储的字符串为"…"(20MB),此时将分配 22 Bytes = 20(len) + 1(free) + 1(null terminator)

惰性空间释放策略,SDS避免了缩短字符串时所需的内存重分配操作,并为将来可能有的增长操作提供了优化。
具体来讲,当我们对现存的SDS字符串进行裁剪后,被裁剪的字符串所占用的空间不会被立即释放,当操作系统需要使用的时候才会释放。

二进制安全的
这一点比较容易理解,在C语言字符串是不允许有空字符的,否则程序将编译报错。所以C语言中的char性数组只能用来存储字符数据,无法存储图像、视频等二进制数据。
在Redis的SDS中,虽然也同样遵循在末尾使用’\0’来表示字符串结尾,但是在中间允许有空字符的存在,所以这也是为啥将SDS的buf称为字节数组。

总结

本篇文章分享了Redis的字符串编码结构SDS,了解了它的定义和内存分配策略。以及和C语言字符串的区别。希望你能从本篇文章中获取新的东西。

参考

《Redis设计与实现》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/61227.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

f1tenth仿真设置

文章目录 一、安装依赖二、进入工作空间克隆三、编译四、运行 一、安装依赖 tf2_geometry_msgs ackermann_msgs joy map_server sudo apt-get install ros-noetic-tf2-geometry-msgs ros-noetic-ackermann-msgs ros-melodic-joy ros-noetic-map-server 二、进入工作空间克隆…

爬虫018_urllib库_cookie反爬_post请求百度翻译获取百分翻译内容_以及详细翻译内容---python工作笔记037

然后我们来看如何用urllib发送post请求,这里我们 用百度翻译为例 我们翻译一个spider,然后我们看请求,可以看到有很多 找到sug这个 可以看到这里的form data,就是post请求体中的内容 然后我们点击preview其实就是 返回的实际内容 然后请求方式用的post 然后我们把上面的信息…

php代码审计,php漏洞详解

文章目录 1、输入验证和输出显示2、命令注入(Command Injection)3、eval 注入(Eval Injection)4、跨网站脚本攻击(Cross Site Scripting, XSS)5、SQL 注入攻击(SQL injection)6、跨网站请求伪造攻击(Cross Site Request Forgeries, CSRF)7、Session 会话劫持(Session Hijacking…

Centos7.9_解决每次重启机器配置的java环境变量都需要重新source /etc/profile才生效的问题---Linux工作笔记060

这种情况需要把环境变量,java的环境变量在/root/.bashrc文件中也放一份,注意这个文件是隐藏的,默认是,需要进行ls -a才能显示. #jdk export JAVA_HOME/lib/jvm export JRE_HOME${JAVA_HOME}/jre export CLASSPATH.:${JAVA_HOME}/lib:${JRE_HOME}/lib export PATH${JAVA_HOME}/b…

maven Jar包反向install到本地仓库

maven Jar包反向install到本地仓库 需求实现 需求 项目打包时报错&#xff0c;缺少一个jar包。 但是在maven仓库都找不到此jar包&#xff0c;其他人提供了这个jar包。 需要把这个jar包install到本地仓库&#xff0c;使项目能正常打包运行。 实现 使用git bash命令执行以下脚…

详解如何计算字符中的字节数

文章目录 字符概念转义 进制的表示前缀区分后缀区分 什么是ASCII产生表述局限性字节计算 什么是Unicode编码方式UCS-2UCS-4 实现方式UTF的字节序和BOM字节计算 JavaScript中使用字符字符使用缺陷规避字符使用缺陷 MIME 编码Base64编码字节计算QP(Quote-Printable) 总结 字符概念…

C++笔记之将定时器加入向量并设置定时器的ID为i

C笔记之将定时器加入向量并设置定时器的ID为i code review! 文章目录 C笔记之将定时器加入向量并设置定时器的ID为i关于代码中的void operator()() 运行 代码 #include <chrono> #include <iostream> #include <thread> #include <vector>// 定义定时…

http、https笔记

目录 HTTP 基本概念状态码&#xff1a;get和post的区别&#xff1a;http 常⻅字段&#xff1a;http的缺点&#xff1a; HTTP/1.1HTTP/3HTTPSHTTPS和HTTP区别对称加密和⾮对称加密⾮对称加密 HTTP 基本概念 状态码&#xff1a; 1xx 中间状态&#xff0c;比如post的continue 20…

《吐血整理》高级系列教程-吃透Fiddler抓包教程(37)-掌握Fiddler中Fiddler Script用法你有多牛逼-下

1.简介 Fiddler是一款强大的HTTP抓包工具&#xff0c;它能记录所有客户端和服务器的http和https请求&#xff0c;允许你监视&#xff0c;设置断点&#xff0c;甚至修改输入输出数据. 使用Fiddler无论对开发还是测试来说&#xff0c;都有很大的帮助。Fiddler提供的功能基本上能…

可靠性工程师是做什么的?需要哪些能力?

一、可靠性工程师是做什么的&#xff1f; 官方解释&#xff0c;可靠性工程师是通过产品可靠性试验&#xff0c;进行性能评估&#xff0c;并预测如何改进产品或体系的安全性、可靠性、可维护性。 简单来说&#xff0c;客户在使用产品的过程中&#xff0c;会出现各种各样的质量…

模仿火星科技 基于cesium+水平面积测量+可编辑

​ 当您进入Cesium的编辑水平积测量世界&#xff0c;下面是一个详细的操作过程&#xff0c;帮助您顺利使用这些功能&#xff1a; 1. 创建提示窗&#xff1a; 启动Cesium应用&#xff0c;地图场景将打开&#xff0c;欢迎您进入编辑模式。 在屏幕的一角&#xff0c;一个友好的提…