看过了《redis设计与实现》,了解了redis设计的基本原理。但是三个月之后发现,遗忘了很多,只好重新温习。只是,这次是从redis源码的角度学习的,当然过程中会翻阅《redis设计与实现》这本书,毕竟需要一个框架指导。按照redis源码的阅读顺序,按照从基本到复杂、从redis使用的基本数据结构开始。本文就介绍redis中使用的最常见的数据结构之一---sds(simple dynamic string),当然是从源码的角度进行解释的。大致的原理,读者可以参考《redis设计与实现》第一章。
redis源码中,关于sds的代码分布在sds.h和sds.c两个文件夹中。本文主要配合源码介绍redis代码中关于sds实现的重要思想。
sds是基于c语言中的char*实现的,存储的字符串都是char*类型的。这从sds的定义可以看得出来( typedef char* sds;),这可以让redis直接使用c语言的字符串的很多函数,而不必自己实现一些在c语言中已有的函数。
但是,redis中实现的sds与一般的c语言的字符串又不一样。sds除了存放字符串本身的数据之外,sds还分别存放着字符串相关的信息,如字符串的长度、未使用的空间的长度,同时针对不同的字符串的长度采用五种不同的说明字符串信息的头结构,最大化节约内存;sds使用空间预分配和惰性空间释放策略,减少内存中空间分配的次数,提高该数据库的性能。下面针对redis中使用的五种不同的字符串头结构及其空间预分配和惰性空间释放策略进行介绍。
一、字符串的五种头结构
//sdshdr5与其他几个header结构体不同,不包含alloc字段,而是用flags的高5位来存储。因此不能为字符串分配空余空间。如果字符串需要动态增长,那么他> 就必然要分配内存才行。所以说,这种类型的sds字符串更适合存储静态的短字符串(长度小于32)
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[];
};
上面列出的五种struct就是redis中的sds使用的五种管理字符串的结构头,现在分别对其中的成员变量做解释。
uint(8,16,32,64)_t:是已经占用的字节数,就是buf中存放的字符串的长度,不包括结尾的'\0'。
uint(8,16,32,64)_t:是一共分配的空间的大小,包括使用的和未使用的大小,不包括结尾的'\0'。