csdn的富文本编辑部分表格会乱列,未找到解决方式,先用截图了
1.embstr和raw的区别?
如上图所示,embstr的数据储存空间是连续的
连续空间带来的优势:
-
一次空间的分配、一次空间的释放
-
查找的更快
2.redis字符串是如何储存的?
通过redisObject + sds 存储
RedisObject
typedef struct redisObject {
// 类型 4bit
unsigned type:4;
// 编码 4bit
unsigned encoding:4;
// 对象最后一次被访问的时间 24bit
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
// 引用计数 4bytes
int refcount;
// 指向实际值的指针 8bytes
void *ptr;
} robj;
type:类型
#define REDIS_STRING 0 // 字符串
#define REDIS_LIST 1 // 列表
#define REDIS_SET 2 // 集合
#define REDIS_ZSET 3 // 有序集
#define REDIS_HASH 4 // 哈希表
encoding:记录了对象所使用的编码,也就是说对象使用了什么数据结构作为对象底层实现。
#define REDIS_ENCODING_RAW 0 // 编码为字符串
#define REDIS_ENCODING_INT 1 // 编码为整数
#define REDIS_ENCODING_EMBSTR 2 // 编码为字符串embstr
#define REDIS_ENCODING_HT 3 // 编码为哈希表
#define REDIS_ENCODING_ZIPMAP 4 // 编码为 zipmap
#define REDIS_ENCODING_LINKEDLIST 5 // 编码为双端链表
#define REDIS_ENCODING_ZIPLIST 6 // 编码为压缩列表
#define REDIS_ENCODING_INTSET 7 // 编码为整数集合
#define REDIS_ENCODING_SKIPLIST 8 // 编码为跳跃表
ptr:指向实际保存值的数据结构
lru:对象最后一次被命令程序访问的时间
refcount:该对象被引用的次数
SDS
struct sdshdr {
// 已使用长度 占4位字节
unsigned int len;
// 未使用长度 占4位字节
unsigned int free;
// 字节数组,存储字符串
char buf[];
};
// redis根据字符串的长度创建不同的存储类以达到节约空间的目的。
// 3.2代码逻辑:存储上<44位强制用了sdshdr8,>44的至少使用sdshdr8往后的类,而key存储时有额外的复制逻辑转化并使用到sdshdr5。
// 因此sdshdr5只用在key上,键的底层是sdshdr5,而值的robj底层是根据字符串长度从sdshdr8往后的类开始创建。
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; (=0,只有3位有效位,因为类型的表示就是0到4,所有这个8位的flags 有5位没有被用到)
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
// 已使用长度 占1位字节
uint8_t len;
// 总长度
uint8_t alloc;
// 类的标识位
unsigned char flags; (=1)
// 字节数组,存储字符串
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len;
uint16_t alloc;
unsigned char flags; (=2)
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len;
uint32_t alloc;
unsigned char flags; (=3)
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len;
uint64_t alloc;
unsigned char flags; (=4)
char buf[];
};
3.为什么用SDS,SDS的优势?
全称为simple dynamic string (简单动态字符串)。
Redis是C语言开发的,C语言自己就有字符类型,但是Redis却没直接采用C语言的字符串类型,而是自己构建了动态字符串(SDS)的抽象类型。
序号 | 优势 | 对比 | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 计数方式更快 | C | C采用遍历获取字符串长度,时间复杂度O(n) | ||||||||||||
SDS | 而SDS储存了字符串的长度,时间复杂度O(1) | ||||||||||||||
2 | 杜绝缓冲区溢出 | C | 假设内存中连续空间已存字符串['a','b','c']与['d','e'],此时内存空间如下
如果C中未执行空间重分配,此时将['a','b','c']2改为['a','b','c','f','g'],内存空间如下
字符串['d','e']的内容被意外修改 | ||||||||||||
SDS | 先检查当前剩余空间是否满足修改之后所需的空间,如果不满足会自动将SDS的空间扩展至修改之后所需空间大小 | ||||||||||||||
3 | 减少内存重分配次数 | C | 每次对字符串做操作时都要重新分配空间,如果忘记重分配,会造成内存溢出或泄露 | ||||||||||||
SDS | 存储了未使用空间,实现了空间预分配和惰性空间释放两种优化策略,减少了内存分配次数 (空间换时间) |
4.内存分配?
1.空间预分配策略,不仅分配需要使用的空间,还会额外分配未使用空间
-
判断capacity长度,是否足够存储新增的字符,如果满足,则无需扩容,否则进行扩容;
-
如果len<=1MB,则加倍进行扩容,即len*2,然后继续进行判断;
-
如果len>1MB时,则1次只会增加1MB的空闲空间,然后继续进行判断;
-
字符串长度最大扩容到512MB,超出后报错
2.惰性回收
当字符串收缩时,程序不会立即执行内存重分配来回收收缩后内存多出来的空间,以备将来使用。
5.为什么小于44(3.2版本)/39(3.0版本)个字节时用embstr,大于用raw?
embstr是连续的空间,redis的内存只能开辟32,64这种
a) RedisObject长16字节
b) 3.2后的SDS(非SDS_5)结构体至少有3字节(uint8_t len、uint8_t alloc、unsigned char flags),
3.0前的SDS结构体至少有8个字节(unsigned int len、unsigned int free)
c) 字符串需要存储结束位\0
故3.2后可使用的空间为64-16-3-1=44
3.0前可使用的空间为64-16-8-1=39
3.0长 4 + 4 = 8字节
unsigned int len;
unsigned int free;
3.2长 1+1+1 = 3字节
uint8_t len;
uint8_t alloc;
unsigned char flags;