redis五种数据结构与六种底层实现

1.redis的五种对外暴露的数据结构

1.string 字符串
2.hash 哈希结构
3.list 列表
4. set 集合
5. sorted set 有序集合

2.redis数据结构的六种底层实现

  1. sds (simple dynamic string) 简单动态字符串
  2. dict 字典
  3. intset 整数集合
  4. skiplist 跳表
  5. ziplist 压缩表
  6. quicklist 快速列表

3.redisObject

redisObject本质上是对外暴露的数据结构与底层实现之间的一个桥梁,相关的代码如下,redis版本3.2。

#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */

#define LRU_BITS 24
typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
    int refcount;
    void *ptr;
} robj;

最开始的5种类型,就是redis对外暴露的5种数据结构。而下面的10种类型,则为redis底层存储的6种结构,当然其中有一部分是以前版本现在已经不使用了。

从源码中可以看出,一个redisobj包含有5个字段

  1. type 即前面所说对外暴露的5种数据类型
  2. encoding 对象内部编码方式,即我们前面提到的六种底层实现(有的是历史版本,目前已经废弃)
  3. lru淘汰算法
  4. refcout 引用计数
  5. ptr 数据指针,指向真正的存储数据。

4.SDS

SDS是redis中使用非常广泛的一种底层数据结构,着重看一下SDS的相关内容。

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
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 {
    //记录buf数组中已使用的字节数量
    uint64_t len; 
    //分配的buf数组长度,不包括头和空字符结尾 
    uint64_t alloc;
    // 标志位, 最低3位表示类型,另外5个位没有使用
    unsigned char flags; 
    char buf[];
};

SDS有5中header类型,之所以要定义不同的类型,是为了让不同的字符串使用不同长度的header,达到节省内存的目的。

而在SDS的结构体中,有以下4个字段

  1. len 记录buf数组中已使用的字符数量
  2. alloc 分配的buf数组长度,不包括头与结尾的空串
  3. flags 最低的三位表示header类型,对应下面的五种类型
#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
  1. buf[] 为字符数组,存放真正的字符串数据

以"redis"这个字符串为例,我们看看实际是如何存储的
redis五种数据结构与六种底层实现_第1张图片
SDS同样遵循C语言中字符串以’\0’结尾的观里,并且这个空串不计算在len的长度中,另外还为该串额外分配了一个字节的空间,包括添加到末尾等操作,全由SDS中的函数自己完成,不需要调用者进行额外的操作。这样做的好处,就可以直接使用C语言中一部分字符串函数库中的函数。

那么使用SDS,相比C语言中的字符串,好处在哪里呢?
结合Redis设计与实现,总结有如下几点

1.可以在常数时间内获取字符串的长度

因为c语言中的字符串不记录字符串长度,要想获得长度必须遍历字符串,时间复杂度为O(N)。而SDS中有记录字符串长度的字段len,可以在O(1)时间内就获取字符串长度。

2.杜绝缓冲区溢出

C字符串不记录自身的长度,每次增长或缩短一个字符串,都要对底层的字符数组进行一次内存重分配操作。如果是拼接append操作之前没有通过内存重分配来扩展底层数据的空间大小,就会产生缓存区溢出;如果是截断trim操作之后没有通过内存重分配来释放不再使用的空间,就会产生内存泄漏。
而在redis中通过alloc属性记录总的分配字节数量。通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化的空间分配策略,解决了字符串拼接和截取的空间问题。

3.存放二进制数据

C 字符串中的字符必须符合某种编码(比如 ASCII), 并且除了字符串的末尾之外, 字符串里面不能包含空字符, 否则最先被程序读入的空字符将被误认为是字符串结尾。这样使得C语言中的字符串只能保存纯文本而无法保存其中像视频图片这种二进制数据。

而在SDS中,是以处理二进制的方式来处理存放在buf数组中的数据,因为是二进制,所以不会对里面的数据类型有任何限制,也可以存放视频图像声音这类非文本数据。

你可能感兴趣的:(redis,redis,数据结构,SDS)