redis的数据库对象有五种,分别是字符串对象(key-value),列表对象(list),哈希对象(hash),集合对象(set),有序集合(sort set)。html
这些数据对象来自于底层数据类型的实现,这些数据类型分别为简单动态字符串,链表,字典,跳跃表,整数集合,压缩列表,对象。java
如下是redisObject和底层数据结构的关系redis
redis的键(KEY)由简单动态字符串实现,字符串对象(key-value)也是由简单动态字符串实现的。而简单动态字符串是由C实现的,它的结构以下算法
struct sdshr{ //记录buf中已经使用的字节数量 unsigned int len; //记录buf中未使用的字节数量 unsigned int free; //字节数组,存储字符串 char buf[]; }
在redis3.2版本后,SDS的结构发生了变化,有多种数据结构,根据状况使用数据库
//字符串长度小于32 Byte struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 记录当前字节数组的属性,究竟是哪一个SDS结构 */ char buf[];/* 保存字符串真正的值 */ }; //字符串长度小于256 Byte,大于32 Byte struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* 记录当前数组的长度 */ uint8_t alloc; /* 记录了当前字节数组总共分配的内存大小 */ unsigned char flags; /* 记录当前字节数组的属性,究竟是哪一个SDS结构 */ char buf[];/* 保存字符串真正的值 */ }; //字符串长度小于65536 Byte (64KB),大于256 Byte struct __attribute__ ((__packed__)) sdshdr16 { uint16_t len; /* 记录当前数组的长度 */ uint16_t alloc; /* 记录了当前字节数组总共分配的内存大小 */ unsigned char flags; /* 记录当前字节数组的属性,究竟是哪一个SDS结构 */ char buf[];/* 保存字符串真正的值 */ }; //字符串长度小于4294967296 Byte (4GB),大于65536 Byte struct __attribute__ ((__packed__)) sdshdr32 { uint32_t len; /* 记录当前数组的长度 */ uint32_t alloc; /* 记录了当前字节数组总共分配的内存大小 */ unsigned char flags; /* 记录当前字节数组的属性,究竟是哪一个SDS结构 */ char buf[];/* 保存字符串真正的值 */ }; //字符串长度大于4294967296 Byte (4GB) struct __attribute__ ((__packed__)) sdshdr64 { uint64_t len; /* 记录当前数组的长度 */ uint64_t alloc; /* 记录了当前字节数组总共分配的内存大小 */ unsigned char flags; /* 记录当前字节数组的属性,究竟是哪一个SDS结构 */ char buf[];/* 保存字符串真正的值 */ };
链表是redis对象列表(list)的一种实现。当列表中元素数量比较多,或元素中字符串数量比较大时,就会使用链表结构。链表方便顺序查询,同时也方便插入和删除。数组
链表由listNode和list组成,其结构以下数据结构
typedef struct listNode{ struct listNode *prev;//前一个节点 struct listNode * next;//后一个节点 void * value;//节点值 }
typedef struct list{ //头节点 listNode * head; //尾节点 listNode * tail; //长度 unsigned long len; //复制函数 void *(*dup) (void *ptr); //释放函数 void (*free) (void *ptr); //对比函数 int (*match)(void *ptr, void *key); }
结构图以下:函数
字典能够用来保存键值对,字典的结构和java的hashMap结构类似,采用hash算法保存key。字典由字典,哈希表,哈希表节点实现。其结构以下:ui
/* * 哈希表节点 */ typedef struct dictEntry { // 键 void *key; // 值,能够是三种形式:指针,8字节字符,8字节整数 union { void *val; uint64_t u64; int64_t s64; } v; //指向下个哈希表节点,造成链表(以下hash冲突,会造成链表) struct dictEntry *next; }
/* * 哈希表 */ typedef struct dictht { // 哈希表数组 // 里面存储dictEntry dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩码,用于计算索引值 // 老是等于 size - 1,index = hash & sizemask // 这样全部计算得出的hash值均可以对应于table的索引 unsigned long sizemask; // 该哈希表已有节点的数量 unsigned long used; }
/* * 字典 */ typedef struct dict { // 类型特定函数,type和privdata是为了多态字典所设置的 dictType *type; // 私有数据 void *privdata; // 哈希表,每一个字典包含两个table // ht[0]是平时使用的哈希表,ht[1]是rehash时使用的哈希表 dictht ht[2]; // rehash 索引标识 // 当 rehash 不在进行时,值为 -1 int rehashidx; }
字典结构图以下:编码
跳跃表是一种有序的数据结构,它经过score进行排序。跳跃表有层级的概念,每一个跳跃表节点有多层,每一个层级会按顺序指向以后若干节点的对应层级。这是为了便于查询,经过层级查询节点,会比按score顺序依次查询要快不少。节点和链表的结构以下:
typedef struct zskiplistNode{ //层 struct zskiplistLevel{ //前进指针 struct zskiplistNode *forward; //跨度 unsigned int span; } level[]; //后退指针 struct zskiplistNode *backward; //分值 double score; //成员对象 robj *obj; }
typedef struct zskiplist { //表头节点和表尾节点 struct zskiplistNode *header,*tail; //表中节点数量 unsigned long length; //表中层数最大的节点的层数 int level; }
结构图以下:
当一个集合的元素数量不大,且类型都为整数时,redis就会使用整数集合进行存储。它的结构以下:
typedef struct intset{ //编码方式 uint32_t enconding; // 集合包含的元素数量 uint32_t length; //保存元素的数组 int8_t contents[]; }
压缩列表是列表对象和哈希对象的实现方式之一。当一个列表的元素数量很少,且值为整数和不长的字符串,这时redis会使用压缩列表。一样若是一个哈希对象的key和value的值数量很少,且为整数和不长的字符串时,redis也会使用压缩列表
参考资料: