禅与计算机 禅与计算机
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Redis

    • Redis核心知识小结
    • Redis源码与实战剖析小结
    • Redis系列文章全汇总
    • 解锁Redis发布订阅模式:通过实践演示挖掘消息通信潜能
    • 掌握 Redis 事务,提升数据处理效率的必备秘籍
    • 基于Jedis来探讨池化技术
    • Redis主从复制技术:理论基础、运行逻辑与应用场景
    • 聊聊Redis主从复制
    • Redis的哨兵模式详解
    • 深度剖析 Redisson 分布式锁:原理、实现与应用实践
    • 来聊聊Redis中的字符串对象的设计
    • 详解redis单线程设计思路
    • 基于Gdb快速上手调试Redis
    • 聊聊redis中的有序集合
    • 来聊聊redis文件事件驱动的设计
    • 如何理解redis是单线程的
    • 来聊聊Redis所实现的Reactor模型
    • 来聊聊Redis客户端的概念
    • 来聊聊redis数据库的设计与实现
    • 来聊聊Redis定期删除策略的设计与实现
    • 聊聊Redis中缓存淘汰算法的实现
    • Redis RDB持久化源码深度解析:从原理到实现
    • 一文读懂Redis RDB持久化:策略、配置与应用
    • 来聊聊redis的AOF写入
    • 来聊聊Redis的AOF重写机制
    • 来聊聊Redis持久化AOF管道通信的设计
    • Redis如何高效实现定时任务
    • 以从节点的角度看看Redis主从复制的实现
    • Redis哨兵是如何完成初始化的
    • 聊聊Redis哨兵选举与故障转移的实现
    • 来聊聊Redis哨兵如何主观认定下线
    • 来聊聊redis的发布订阅设计与实现
    • 来聊聊去中心化Redis集群节点如何完成通信
    • redis集群中如何处理非本节点的slot
    • 来聊聊redis集群数据迁移
    • 硬核详解redis客户端指令与服务端传输协议RESP
    • 从redis源码了解双向链表的设计与实现
    • 能不能给我讲讲redis中的列表
    • 聊聊redis中的字典设计与实现
    • 聊聊redis字典指令操作
    • 高效索引的秘密:redis跳表设计与实现
    • 探索数据结构之美——有序集合的内部机制
    • Redis SDS动态字符串深度解析
      • 写在文章开头
      • 详解SDS字符串
        • 为什么需要SDS字符串
        • SDS基本定义和逻辑结构
        • SDS如何完成字符串的创建
        • SDS高效获取字符串长度
        • Redis如何实现高效的字符串拼接
        • SDS字符串的惰性空间释放
      • 小结
      • 参考
    • Redis核心数据结构字典操作实践与解析
    • Redis持久化技术AOF要点与详细解答
    • Redisson全面解析从使用方法到工作原理的深度探索
    • 基于VSCode调试Redis源码指南
    • Redis持久化技术AOF要点与详细解答(2)
  • MySQL

  • ElasticSearch

  • StarRocks

  • 数据库
  • Redis
sharkchili
2026-03-25
目录

Redis SDS动态字符串深度解析

[toc]

# 写在文章开头

我们都知道Redis基于单线程实现的一个高性能内存数据库,所以了解其底层设计,会让我们具备一个从微观的视角极致压榨Redis性能的能力,这其中对于数据结构的设计也是非常巧妙,所以关于Redis源码解析的系列将直接从最基本的字符串的设计说起。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili 。

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

# 详解SDS字符串

# 为什么需要SDS字符串

C语言自带的字符串通过一个char*字符串数组存储字符构成一个完整的字符串,判断当前字符串是否到达末尾则是通过判断末尾是否有\0作为依据。获取字符串长度操作的时候,也是通过指针进行遍历遇到第一个\0则说明当前字符串到达结尾了。

这一点我们可以直接在Linux上编写一个简单的C语言代码印证:


#include <stdio.h>
#include <string.h>

int main()
{
        char *a = "redi\0s";
        char *b = "redis\0";
        printf("%lu\n", strlen(a));
        printf("%lu\n", strlen(b));
        return 0;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14

通过gcc指令生成可执行文件后输出结果如下,可以看到原生C语言的长度是通过遍历字符串直至遇到第一个结束符\0为止的:

4
5
1
2

这种设计对于Redis来说非常不友好,原因很简单,某些场景下Redis可能会将文件的二进制数据流作为数据串存入,假设字符串存在\0的字符,使用原生的字符串数组无法做到很准确数据截断出现二进制安全问题:

除此之外,在进行字符串拼接操作的时候,C语言原生字符串需要从头到尾遍历到目标位置,然后将新的字符串拼接上去:

char *strcat(char *dest, const char *src) {
	//指向要被拼接的字符串
	char *tmp = dest;
	//遍历到末尾
	while(*dest)
	dest++;
	//将src拼接到desc上
	while((*dest++ = *src++) != '\0' )
	//返回结果
	return tmp;
}
1
2
3
4
5
6
7
8
9
10
11

并且C语言的创建需要预先定义字符串长度,这使得我们如果没有在逻辑上加以控制的话,还可能出现缓冲区溢出的情况:

这种种细节上势必导致单线程操作的Redis存在各种耗时的开销,于是Redis设计者就专门设计了SDS字符串这种数据结构。

# SDS基本定义和逻辑结构

SDS通过三个关键字段来管理字符串:

  • len:记录当前字符串的实际长度
  • free:记录当前分配但未使用的字节数
  • buf[]:实际存储字符串内容的字符数组

那么问题来了,为什么需要free这个字段呢?

假设我们使用SDS创建一个初始内容为Redis的字符串,之后将其修改为Res。SDS的做法除了修改buf数组的字符串内容外,还会同步维护len和free字段。通过维护free字段,确保后续需要再追加2个字符时可以直接利用现有内存,而无需创建全新的数组来存放字符。这也就是所谓的惰性释放的设计思想:

对于这段结构的定义,我们可以redis的sds.h看到这段定义:

// Redis 3.2之前版本的SDS结构体定义
struct sdshdr {
	//字符串长度
    unsigned int len;
    //空闲的数组空间大小
    unsigned int free;
    //字符数组
    char buf[];
};
1
2
3
4
5
6
7
8
9

需要注意的是,在Redis 3.2版本之后,SDS的实现进行了优化,引入了多种头部结构以节省内存。根据字符串长度的不同,Redis会选择不同的结构体类型:

  • sdshdr5:用于存储长度小于32字节的字符串(不存储free字段)
  • sdshdr8:用于存储长度小于256字节的字符串
  • sdshdr16:用于存储长度小于65536字节的字符串
  • sdshdr32:用于存储长度小于2^32字节的字符串
  • sdshdr64:用于存储长度小于2^64字节的字符串

这种设计通过使用不同大小的整型来存储长度和空闲空间,避免了小字符串的空间浪费。

# SDS如何完成字符串的创建

我们先从SDS字符串的创建聊起,进行字符串创建时Redis会调用sdsnewlen函数进行创建,假设我们要创建一个字符串abc,那么对应的创建代码就是:

mystring = sdsnewlen("abc",3);
1

首先Redis会为abc创建一个长度为3+1的数组,然后将abc放到数组前3个位置,末尾用\0收尾,同时记录此时字符串的长度为3(不包括结束符),free为0即当前空间全部填满:

对应的我们也给出sds.c中这段代码的实现:

sds sdsnewlen(const void *init, size_t initlen) {
    struct sdshdr *sh;
    //初始化创建一个sdshdr加上(字符串长度+1)的内存空间
    if (init) {
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    if (sh == NULL) return NULL;
    //记录字符串长度
    sh->len = initlen;
    //空闲空间记录为0
    sh->free = 0;
    //将字符串存到buf数组中,完成后buf数组用\0收尾
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    sh->buf[initlen] = '\0';
    //返回buf指针
    return (char*)sh->buf;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# SDS高效获取字符串长度

相较于C语言默认字符串通过遍历字符串获取长度的设计,SDS这种通过创建时即实时维护长度字段的设计实现了高效的O(1)级别字符串长度获取操作:

static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    //通过sds的len字符快速得到当前字符串的长度
    return sh->len;
}
1
2
3
4
5

# Redis如何实现高效的字符串拼接

假设我们用sdsnewlen创建字符串red,此时我们的buf为red,len为3,而free则是0。此时希望将is拼接上去。很明显当前buf数组空间不够,基于空间预分配思想,Redis认为本次拼接操作之后可能还会存在拼接的情况,所以计算出拼接后的字符redis长度为5字节,在此基础上再开辟5+1的空间作为预分配空间+结束符空间,也就是创建一个全新的容量为11的数组空间装redis字符串。由此避免下次可能出现字符串拼接导致在此创建新数组的开销:

对应的我们给出拼接函数sdscat,可以看到其入参为当前字符串对象s和要拼接的字符串指针t:

PS : Redis的空间预分配机制并非一成不变采用拼接后的字符串长度*2+1,当当前字符串空间小于1MB时,预分配的空间是所需空间的两倍;当字符串长度大于等于1MB时,每次预分配的空间永远都是1MB。例如我们当前字符串大小为2MB,那么进行拼接操作扩容时得到的空间大小则是2MB+1MB+1byte:

对应的我们给出拼接函数sdscat,可以看到其入参为当前字符串对象s和要拼接的字符串指针t:

sds sdscat(sds s, const char *t) {
    return sdscatlen(s, t, strlen(t));
}
1
2
3

查阅其底层实现可知,

sds sdscatlen(sds s, const void *t, size_t len) {
    struct sdshdr *sh;
    //获取当前长度
    size_t curlen = sdslen(s);
	//空间预分配,如果当前空间不够且小于1MB则基于拼接后的长度*2+1,反之基于拼接后的字符串长度+1MB+1
    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;
    sh = (void*) (s-(sizeof(struct sdshdr)));
    //将字符串存入buf数组
    memcpy(s+curlen, t, len);
    //长度为拼接后的长度
    sh->len = curlen+len;
    //空闲空间为预分配后得到的free减去新字符串长度len
    sh->free = sh->free-len;
    //字符串末尾用\0收尾
    s[curlen+len] = '\0';
    return s;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

为保证文章的连贯性,我们也给出空间预分配的代码:

sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;
    size_t len, newlen;
    char *newbuf;
    
    // 获取当前的sdshdr结构
    sh = (void*)(s - sizeof(struct sdshdr));
    len = sh->len;
    newlen = len + addlen;
    
    // 如果当前的free空间足够,直接返回
    if (sh->free >= addlen) return s;
    
    // 根据新长度决定预分配策略
    // 如果新长度小于1MB则newlen为拼接后的长度*2,反之则是newlen+1MB
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
        
    // 基于计算的newlen创建出sh+newlen+结束符大小的新空间newsh 
    newsh = zrealloc(sh, sizeof(struct sdshdr) + newlen + 1);
    if (newsh == NULL) return NULL;
    
    // 空闲空间为新长度减去新字符串长度len
    newsh->free = newlen - len;
    // 返回字符数组指针
    return newsh->buf;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# SDS字符串的惰性空间释放

进行SDS字符串缩减操作时,Redis设计者为了避免后续还可能存在的拼接操作,采用了惰性空间释放的机制。例如我们希望将redis字符串改为red,SDS的做法是直接在d后面追加\0,并同时维护len和free字段的值,而不会重新分配一个新的buf数组空间。这样既保证了空间缩减的执行性能,也预防了未来可能的拼接操作需要重新分配内存:

对应我们给出惰性空间释放的代码:

sds sdscpy(sds s, const char *t) {
	//直接调用sdscpylen入参为当前字符串s和替换的字符串指针t
    return sdscpylen(s, t, strlen(t));
}

sds sdscpylen(sds s, const char *t, size_t len) {
    struct sdshdr *sh = (void*)(s - sizeof(struct sdshdr));
    size_t totlen = sh->free + sh->len;
    
    // 如果当前空间足够容纳新字符串,直接复制
    if (totlen < len) {
        // 空间不够,需要重新分配
        s = sdsMakeRoomFor(s, len - totlen);
        if (s == NULL) return NULL;
        sh = (void*)(s - sizeof(struct sdshdr));
    }
    
    // buf指向字符串t
    memcpy(s, t, len);
    // 尾部用\0收尾
    s[len] = '\0';
    // 维护当前长度len和空闲空间大小free
    sh->len = len;
    sh->free = totlen - len;
    return s;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 小结

本文从Redis源码的角度剖析了SDS字符串的设计思想和实现方式,希望对你有所启发。

SDS作为Redis的核心数据结构之一,通过以下几个关键设计提升了字符串操作的效率和安全性:

  1. 二进制安全:通过len字段记录长度,而不是依赖\0作为字符串结束标识,可以安全地存储包含\0的二进制数据。

  2. 高效获取长度:通过len字段可以在O(1)时间内获取字符串长度,而C字符串需要O(N)时间遍历。

  3. 空间预分配:在字符串增长时,SDS会预分配额外空间,减少内存重分配次数,提高性能。

  4. 惰性空间释放:在字符串缩短时,SDS不会立即释放空间,而是保留在free字段中供后续使用,避免频繁的内存重分配。

  5. 内存优化:Redis 3.2之后引入了多种SDS头部结构,根据字符串长度选择最适合的类型,避免小字符串的空间浪费。

这些设计使得SDS在Redis中能够高效地处理各种字符串操作,为Redis的高性能提供了基础保障。

我是 sharkchili ,CSDN Java 领域博客专家,开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。

# 参考

《Redis设计与实现》

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
探索数据结构之美——有序集合的内部机制
Redis核心数据结构字典操作实践与解析

← 探索数据结构之美——有序集合的内部机制 Redis核心数据结构字典操作实践与解析→

最近更新
01
基于EasyExcel实现高效导出
03-25
02
从开源框架中学习那些实用的位运算技巧
03-25
03
浅谈分布式架构设计思想和常见优化手段
03-25
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×