Redis中String数据类型的SDS设计精妙
SDS(Simple Dynamic String)有5种自动内存对齐的结构体(sdshdr5
, sdshdr8
, sdshdr16
, sdshdr32
, sdshdr64
),用于实现动态字符串(根据char *
字符串长度选择最小的一种结构体),其中sdshdr5
结构体仅用来直接读取flags,其余四种结构体的内部均使用char []
数组(可看作指针)指向char *
。
解释:通过
typedef char *sds;
定义sds
为char *
,也就是说sds
的原本还是char *
,只是通过sdshdr
结构体对这个char *
进行了类型描述,可根据char *
的大小自动选择一种结构体来表示它。之所以称动态,是因为在程序运行中可对char *
进行再分配(扩容/缩减),而分配后char *
的大小就发生了变化,这时就需要能表示它的最小sdshdr
结构体来对它进行类型描述。
四种结构体的定义(伪代码):
struct __attribute__ ((__packed__)) sdshdr{8|16|32|64} {
uint{8|16|32|64}_t len; // 记录实际char的个数(字符串的实际长度)
uint{8|16|32|64}_t alloc; // 记录可用的buf大小,已减去结构体sdshdr{8|16|32|64}的大小和1个尾部‘\0’字符。
unsigned char flags; // 低3位表示类型,高5位表示字符串长度。
char buf[]; // 最大可分配1024*1024个字节(512M),不会发生溢出;它因内存对齐而巧妙地指向sds原本的char *。
};
设计精妙之处:以结构体sdshdr64
为例,自动内存对齐后内部成员的排列顺序及占用大小。其中void *sh
和char *sds
指针分别指向结构体在内存中的地址,其各自的含义另见Redis 6.2源码,在Redis中对sds的操作是通过前述两个指针运算(加减移动)来完成的。
#注意:*表示1个字节已用,_表示1个字节未用。 # #未对齐的sdshdr64大小为24个bytes: |len |alloc |flags |buf ------------------------------------- |********|********|*________| # #已对齐的sdshdr64大小为17个bytes: flags|len |alloc |buf ------------------------------------- *|********|********| # #sds(char*)指向的地址: flags|len |alloc |buf ------------------------------------- *|********|********| ↑ sds(char *) # #sh(void *)指向的地址: flags|len |alloc |buf ------------------------------------- *|********|********| ↑ sh(void *) = sds(char *) + sizeof(sdshdr64) # #从sh(void *)移到sds(char *): flags|len |alloc |buf ------------------------------------- *|********|********| ↑ sds(char *) = sh(void *) - sizeof(sdshdr64)
如果你对本文有补充,请转发此文章,并在自己的文章中补充说明。