Redis 基础数据结构
redis基础数据结构:动态字符串SDS、intSet、Dict、ZipList、QuickList、SkipList、RedisObject。
数据结构千万别和数据类型混淆。Redis还包括五大数据类型:String、List、Set、SortSet、Hash。
数据结构——动态字符串SDS
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:已保存的字符串字节数,不包括结束符。表示字节数最大长度8个bit,也就是2^8 -1 */
uint8_t alloc; /* buf申请的总字节数,不包括结束符*/
unsigned char flags; /* 不同SDS 头类型(sdshdr5|sdshdr8|sdshdr16|sdshdr32|sdshdr64), 用来控制SDS 头的大小*/
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[];
};
如上就是redis 定义的SDS五种数据结构。其中以sdshdr8
结构体为例:
- uint8_t len:
代表无符号8bit,定义已保存字符串字节数的长度。因为最大支持8bit,所以表示最大的长度范围是(2^8-1)-1
,最后一个1代表结束符。 - uint8_t alloc:
代表无符号8bit,定义buf申请的总字节数,不包括结束符。因为存在内存预分配机制,可能会导致alloc和len长度不一致。 - flags:
单字节,定义SDS头类型。存在五种不同的SDS数据结构。如下:#define SDS_TYPE_5 0 -> sdshdr5 (头类型对应的SDS数据结构) #define SDS_TYPE_8 1 -> sdshdr8 #define SDS_TYPE_16 2 -> sdshdr16 #define SDS_TYPE_32 3 -> sdshdr32 #define SDS_TYPE_64 4 -> sdshdr64
- char buf[]:
字节数组,存储字符串的字节数组。
SDS 内存预分配机制
上述以value值存SDS结构为例。
-
执行set name hello:初始化sds结构,底层自动申请内存,初始化时
len=alloc
,hello占5个字节,所以len=alloc=5
(二者都不包含结束符)。由于字节数5,远远小于2^8大小,所以用sdshdr8
结构绰绰有余。sdshdr8
对应的flag为#define SDS_TYPE_8 1
,所以flag=1。 -
执行set name hello,amy:此时申请的内存
alloc=5
不足以存储新数据,需要重新申请内存。- 如何内存预分配?
-
如果新字符串小于1M,则新空间=扩展后字符串长度的两倍+1(+1是结束符)
-
如果新字符串大于1M,则新空间=扩展后字符串长度+1M+1
-
- 为什么要内存预分配?
-
若每次重新赋值都需要重新申请内存,会造成申请内存的开销
-
频繁申请内存会造成用户态和内核态频繁切换的开销
-
- 如何内存预分配?
优缺点
C语言字符串 | SDS数据结构 | |
---|---|---|
获取长度 | C字符串底层是字节数组,且以指针指向,需遍历获取。时间度O(n) | Header定义len,只需读取len。时间度O(1) |
扩容 | 由于指针指向字符串数组,不允许修改源字符串。可重新申请变量指向新字符串地址 | 存在内存预分配机制,且存在5种类型的SDS,支持修改和扩容 |
内存分配 | 存在内存预分配机制,减少内存分配次数,减少内核态和用户态的切换 | |
二进制安全 | C字符串是以\0为结束符,不支持字符串内存在\0 | 以header中的len为结束标识,支持字符串包含\0等字符 |