Redis之String类型底层的存储形式

Redis中的String类型采用int、embstr和raw三种编码方式以节省内存。embstr适用于44字节以下的字符串,超过则转为raw。这种划分基于内存分配策略,减少了内存碎片。embstr与raw的区别在于内存布局,前者更紧凑。当字符串增长或使用APPEND命令时,embstr可能转为raw存储。
摘要由CSDN通过智能技术生成

前置:Redis底层是怎样保存一个key和value的?

Redis中String类型的三种编码

String类型在底层对应的有三种编码

在这里插入图片描述

int: 当value为long类型的整数值且长度小于等于20字节时

在这里插入图片描述
redis启动时会先建立10000个redisObject,值为0 - 9999的值,将这10000个redisObject作为共享对象。所以如果我们set的值在0 - 10000之间,则指向共享对象,不需要创建新的redisObject。

在redis源码的object.c文件中有一个tryObjectEncoding()函数,该函数的作用是尝试对字符串对象进行编码以节省空间。
在这个函数中有这样一段代码:
在这里插入图片描述

raw:当value为大于44字节的字符串时

在这里插入图片描述

embstr:当value为小于44字节的字符串时

在这里插入图片描述

为什么embstr 形式,可以存储最大字符串长度是44字节?

首先我们需要看一下如果存储一个String类型的value,至少需要占用多少的内存空间。

在之前的一篇文章中,我们提到,redis中的value其实是以RedisObject的方式存储的。

再来看一下RedisObject的定义和RedisObject中每个属性占用的内存空间:

typedef struct redisObject {
    unsigned type:4;  			// 占用4bits, 表示具体的数据类型
    unsigned encoding:4;		// 占用4bits, 表示具体的编码方式
    unsigned lru:LRU_BITS; 		// 占用24bits, 最近一次被访问的时间
    int refcount;				// 占用4bytes, 对象引用计数
    void *ptr;					// 占用8bytes, 指向真正的数据地址的指针

} robj;

我们看到,一个RedisObject至少需要占用 4bits + 4bits + 24bits + 4bytes + 8bytes = 16byte

其中ptr是指向真正数据的内存地址的指针。如果我们存储的是字符串类型的数据, 这个指针就指向定义的SDS的地址。

我们再来看一下,sds的定义。

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len;  				// 占用1byte, 表示当前字符数组长度
    uint8_t alloc; 				// 占用1byte, 当前字符数组总共分配的内存大小
    unsigned char flags; 		// 占用1byte, 表示当前字符数组的sdshdr类型。用来标识到底是sdshdr8 还是sdshdr16等
    char buf[];  				// 占用nbyte, 字符串真正的值,占用空间根据值的长度决定
};

可以看出,一个sds类型的数据,至少需要占用 1byte + 1byte + 1byte = 3 byte 的空间,这是在字符串值为空的情况下。

到这里我们可以分析出,redis要存储一个字符串类型的value,至少占用16bytes + 3bytes = 19bytes空间 (redisObject + sds)

而内存分配器分配内存大小的单位都是2的n次方,即 2/4/8/16/32/64/…,而redis定义了超过64字节的就属于大字符串了。所以在64byte以内的非long类型字符串都会以embstr类型来存储。而超过了64字节的话,会用raw的形式来存储。

所以 64 - 19 = 45字节。这里留给真正存储字符串值的空间就是45字节。因为字符串都是以\0结尾的,再去除一个字节,就剩44字节了。

所以,到这里得出,embstr 形式,可以存储最大字符串长度是44字节。而超过了这个长度,会使用raw来保存。

为什么要将字符串分为不同的方式来进行存储?

这里需要注意的是,embstr和raw类型的存储方式也有一些差异。

embstr形式存储,RedisObject和SDS内存中空间是连续的,而raw类型,则不连续。
在这里插入图片描述

所以,Redis对字符串以多种编码方式进行存储,可以节省内存空间,减少内存碎片。对于<= 44字节的字符串,使用embstr类型存储确实是更好的方式。

什么情况下embstr会转为raw?

  1. 当字符串长度超过44字节时

  2. 当对原字符串调用APPEND 命令追加时,这时,即使字符串长度不超过44,也会转为raw类型来进行存储。
    在这里插入图片描述

Redis中的String是基于SDS(Simple Dynamic String)实现的,SDS是Redis自己实现的一个字符串类型。SDS和C语言中的字符串一样,是一块连续的内存空间,但是它还增加了一些其他的信息,比如它的长度、当前已使用的空间和总空间等。SDS的结构如下: ```c struct sdshdr { int len; //字符串长度 int free; //未使用空间长度 char buf[]; //字符串内容 }; ``` Redis中的String底层主要是通过SDS实现的,String的操作都是通过SDS的相关函数实现的,如: - set:创建一个SDS,将需要存储字符串拷贝到SDS中,并返回一个指向SDS的指针。 - get:根据输入的key,查找对应的SDS,并返回它的指针。 - append:根据输入的key和value,查找对应的SDS,将value追加到SDS的尾部,同时更新SDS的长度和未使用空间的长度。 - incr:根据输入的key,查找对应的SDS,将SDS中存储的数字加上输入的值,然后将新的数字转换成字符串存储回SDS中。 - decr:和incr类似,只是将SDS中存储的数字减去输入的值。 - setrange:根据输入的key和offset,查找对应的SDS,在SDS中从offset开始替换指定长度的字符串。 - getrange:根据输入的key、start和end,查找对应的SDS,返回该SDS的子字符串。 - strlen:根据输入的key,查找对应的SDS,返回该SDS的长度。 综上所述,RedisString底层主要是通过SDS实现的。在SDS的基础上,Redis提供了一些方便数据操作的接口,使得开发者可以更加方便地进行数据存储和操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值