前言
Redis 中有五种数据类型,分别是 Str (字符串)、 Hash (哈希)、 List (列表)、 Set (集合)、 Zset (有序集合)。 这五种数据类型的实际运用与底层实现和其他语言中的数据结构的实现有所不同,底层实现是由Redis基于C语言的基础上来完成的。接下来我们会结合源码来讨论 Redis 字符串中的实际运用与底层原理。
运用范例
redis 127.0.0.1:6379> SET name "spacedong"OKredis 127.0.0.1:6379> GET name"spacedong"复制代码
- 一个String键能够存储512M的数据。
- String类型是二进制安全的,可以存储的数据是任何形式的,比如序列化的对象,图片等。
- 范例中的键值对中的 name 这个键是一个 SDS 字符串,"spacedong"也是一个 SDS 字符串。
特性详解
- String 的字符串底层是由动态字符串(simple dynamic string ,SDS)来实现的。
struct sdshdr { // 记录 buf 数组中已使用字节的数量 // 等于 SDS 所保存字符串的长度 int len; // 记录 buf 数组中未使用字节的数量 int free; // 字节数组,用于保存字符串 char buf[];};复制代码
- SDS 字符串中的结构
- free 是指在 SDS 中未使用的空闲字符空间,如图为 5
- len 是在 SDS 中实际使用的长度,如图为 5
- 在 SDS 中是会额外申请一个空字符来保存结尾的字符,这个和C语言中的字符串是一样。
- 获取 SDS 的字符串长度
在 SDS 中获取实际保存字符串的长度方法(源码如下)
/* * * 返回 sds 实际保存的字符串的长度 * * T = O(1) */static inline size_t sdslen(const sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); return sh->len;}复制代码
** 在 SDS 中获取未使用空间的方法**(源码如下)
/* * 返回 sds 可用空间的长度 * * T = O(1) */static inline size_t sdsavail(const sds s) { struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); return sh->free;}复制代码
- SDS 中防止缓冲区溢出的策略
C语言中的字符串在扩容的时候,已经假设为该字符串分配了足够的内存空间了。如果没有分配的话,那么所以当有需要扩容的字符串时会产生缓冲区溢出,在 SDS 中的则是在每次扩容的时候先进行判断,是否有足够的空间来存储传进来的字符串,如果没有,则是扩容到相对应的长度,同时对 free 属性分配相对应的长度。
例如一开始的使用长度为 5 ,需要扩容的长度为 5 ,那么free分配的长度就为10。这个是与 SDS 的空间分配策略有关。
- SDS 修改字符串时策略及好处
在字符串进行修改的时候,会涉及到内存的重分配,这是一个复杂的操作,有可能涉及到系统的调用,SDS 在修改字符串的时候的策略可以为减少内存重分配的次数。
1. 空间预分配策略
具体的策略是在字符串扩容时,会在扩容后为 free 属性分配一个合适的长度,这样的目的是为下次的字符串扩容的时候提前分配好了一定的内存。
这里的合适长度是指:如果 SDS 字符串的长度为小于1M,那么分配和实际使用长度相同的内存,如修改后的字符串长度为13个字符,那么为 free 分配 13 个字符的内存,如果修改后的 SDS 长度大于 30M,那么为 free 分配的长度为 1M 的内存。
2. 惰性空间策略
如果 SDS 字符串的长度在修改后变短的话,那么空余出来的长度分配给 free ,为的是下次的字符串的使用。
3. 好处
可以让内存分配的次数从一定为 N 次变为最多是 N 次。同时也可以满足 redis 中的对数据频繁修改的要求。
- 二进制的安全数据
在 SDS 中存的是二进制的数据格式,这种格式的数据存进来是什么样的,取出来的时候也是怎么样的,同时也是满足存储多种数据格式的数据的要求,如存储图片,文本数据等。