有趣的Redis:数据结构详解-sds

在这里插入图片描述

介绍

我们知道Redis是一个键值对数据库,当你执行如下命令时

set testKey testValue;
rpush fruits banana apple;

其中的键就是用sds(Simple Dynamic String,简单动态字符串)来实现的,redis中string类的值也是用SDS实现的(如上面的testValue)。我们来看一下SDS的底层数据结构是啥?

3.0版本及以前

struct sdshdr {
    
    
	// buf数组中已使用字符的数量
    unsigned int len;
    // buf数组中未使用字符的数量
    unsigned int free;
    // 字符数组,用来保存字符串
    char buf[];
};

如下是一个sds的示例
在这里插入图片描述
free:free为0,表示未使用的空间
len:这个sds保存了5字节长的字符串
buf:char类型的数组,前5个字节是字符串,后一个字节是\0,表示结尾,\0不计入总长度

当要存的字符串变大或者变小的时候,会造成频繁的内存分,进而影响性能。所以Redis通过空间预分配和惰性空间释放策略来避免内存的频繁分配

空间预分配

当sds的内容变大时,程序不仅会为sds分配修改所需要的空间,还会为sds分配额外的未使用的空间。

  1. 对sds进行修改之后,sds的长度(即len属性的值)小于1MB,程序分配和len属性同样大小的未使用空看,即sds len属性的值和free属性的值相同
  2. 对sds进行修改之后,sds的长度(即len属性的值)大于等于1MB,程序会分配1MB的未使用空间

惰性空间释放

当sds内容变小时,程序并不会释放缩短后剩余的空间,只是修改free属性,将未使用字符数量记录下来,等以后使用

3.0版本以后

上面的结构有改进的空间吗?如果让你来写sds,你会怎么写呢?

不同长度的字符串是否有必要占用相同大小的头部?

当字符串很小的时候,我们还得额外的使用8个字节(len和free各占4个字节),感觉有点太浪费了。

我们是否可以根据字符串的长度,来决定len和free占用的字节数呢?比如短字符串len和free的长度为1字节就够了。长字符串,用2字节或4字节,更长的字符串,用8字节。

那我们如何区分这3种情况呢?

很简单,再加一个一字节的字段标明类型就行了

所以在redis 3.2中,有如下5种类型

struct __attribute__ ((__packed__)) sdshdr5 {
    
    
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    
    
    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__)) sdshdr16 {
    
    
    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__)) sdshdr32 {
    
    
    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__)) sdshdr64 {
    
    
    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[];
};
  1. len:buf数组中已使用字符的数量
  2. alloc:buf数组中已分配字符的数量
  3. flags:标识当前结构体的类型,低3位用作标识位,高5位预留(3个字节能保存的种类数为2的3次方即8,sds总共的种类数为5,所以用低三位就能用标识种类)
  4. buf:字符数组,用来保存字符串

可以看到sdshdr5比较特殊,并没有len和alloc两个字段,那它是如何存已使用和已分配的数量呢?

在这里插入图片描述

sdshdr5中,flags占用一个字节,低3位表示type,高5位表示长度,能表示的区间长度为(0~31,即25-1)

sdshdr5并没有存在alloc的值,因此它不会进行空间预分配和惰性空间空间释放,长度变动每次都重新申请内存

当字符串长度大于31时,flags的后5位就存不下了,所以我们就将len和free单独存放

参考博客

猜你喜欢

转载自blog.csdn.net/zzti_erlie/article/details/112130664