1 简单动态字符串--simple dynamic string
实现
相对于C字符串
1. 常数复杂度获取字符串长度
2. 杜绝缓冲区溢出
3. 减少修改字符串时带来的内存重分配次数(空间预分配,惰性空间分配)
4. 二进制安全(不仅可以保存文本数据,还可以保存任意格式的二进制数据)
5. 兼容部分C字符串函数
2 链表
实现
list listNode
特点
双端, 无环, 带表头指针和表尾指针, 带链表长度计数器, 多态
3 字典
用处
Redis数据库, 哈希键的底层实现
数据结构
哈希表dictht 哈希表节点dictEntry 字典dict
哈希算法
MurmurHash2算法
键冲突
将新节点添加到链表的表头位置
rehash过程
1) 为ht[1]分配空间
2) rehashindex = 0;
3) 每次CURD时,将ht[0]在reindexhash索引上的所有键值对rehash到ht[1]中, rehashindex++;
4) 待所有键值对都被rehash到ht[1]中, rehashindex = -1.
4 跳跃表--skiplist
时间复杂度
最好O(logN),最坏O(N)
用处
有序集合键的底层实现之一(有序结合中元素比较多,或者有序集合中成员是比较长的字符串时), 集群节点中用作内部数据结构
数据结构
zskiplistNode的结构:
typedef struct zskiplistNode { //层 struct zskiplistLevel { //前进指针 struct zskiplistNode *forward; //跨度 unsigned int span; } level[]; //后退指针 struct zskiplistNode *backward; //分值 double score; //成员对象 robj *obj; } zskiplistNode;
typedef struct zskiplist { //表头节点和表尾节点 struct zskiplistNode *header, *tail; //表中节点的数量 unsigned long length; //表中层数最大的节点的层数 int level; } zskiplist;
5 整数集合--intset
用处
一个集合中只包含整数值元素,并且这个集合的元素数量不多时
复杂度
向整数集合添加新元素的时间复杂度为O(N)
升级的好处
提高灵活性,节约内存
注意
intset不支持降级
6 压缩列表--ziplist
用处
列表键(只包含少量列表项,并且列表项要么就是小整数值,要么就是长度比较短的字符串)和哈希键(只包含少量键值对,并且每一个键值对的键和值要么就是小整数值,要么就是长度比较短的字符串)的底层实现之一
实现
其中,previous_entry_length属性长度为一字节,五字节,记录了前一个节点的长度,所有程序可以通过指针运算,根据当前节点的起始地址来计算前一个节点的起始地址.
encoding属性记录了content属性所保存数据的类型及长度,一字节,二字节,五字节,值的最高位为00,01,10,表示保存字节数组,其他位表示字节数组的长度;一字节的最高位为11,表示整数,类型和长度由其他位确定.
连锁更新
添加新节点,删除节点都可能触发连锁更新
连锁更新的最坏时间复杂度为O(N²),但ziplistPush等命令的平均时间复杂度为O(N).
7 对象
Redis基于以上的数据结构创建了一个对象体系,包含了字符串对象,列表对象,哈希对象,集合对象,有序集合对象这五种对象.
Redis的对象体系还实现了基于引用计数技术的内存回收机制,同时基于引用计数技术实现了对象共享机制,在适当条件,通过多个数据库键共享同一个对象来节约内存.
Redis中的每一个对象都由一个redisObject结构表示:
1 typedef struct redisObject { 2 //类型 3 unsigned type:4; 4 5 //编码 6 unsigned encoding:4; 7 8 //指向底层实现数据结构的指针 9 void *ptr; 10 11 // ... 12 } robj;
refcount表示引用计数;
lru记录了对象最后一次被应用程序使用的时间,空转时间为当前时间减去lru的值,如果服务器打开了maxmemory选项,并且回收内存算法为volatile-lru,allkeys-lru,那么当内存占用数超过maxmemory的上限,那么空转时间较高的那部分键将会被优先释放;
type表示对象的类型,键总是一个字符串对象,值可以是五种对象之一;
encoding记录了对象所使用的编码,
7.1 字符串对象
7.1.1 类型与编码
如果保存的是整数值,且可用long类型表示,那么编码设为int;
如果保存的是一个字符串,并且长度大于32字节,那么使用SDS保存,编码设为raw;
如果保存的是一个字符串,并且长度小于或等于32字节,那么编码设为embstr;
7.1.2 保存的具体值与编码
可以用long类型保存的整数,编码为int;
可以用long double类型保存的浮点数,编码为embstr或raw;
字符串值,或者长度太长无法用long类型保存的整数,或者长度太长无法用long double类型保存的浮点数,编码为embstr或raw.
7.1.3 转换
Redis中embstr编码的字符串对象是只读的,对之的任何修改,都将使之转换为raw编码的字符串对象.
7.2 列表对象
ziplist编码的列表对象使用压缩列表作为底层实现,
linkedlist编码的列表对象使用双端列表作为底层实现.
7.2.1 编码转换
使用ziplist需满足:
- 所有字符串长度小于64字节;此值可改:list-max-ziplist-value
- 元素数量小于512个;此值可改:list-max-ziplist-entries
7.3 哈希对象
ziplist编码的哈希对象使用压缩列表作为底层实现,
hashtable编码的哈希对象使用字典作为底层实现;其中字典的每一个键值都是一个字符串对象.
7.3.1 编码转换
使用ziplist需满足:
- 所有键值对的字符串长度都小于64字节;此值可改hash-max-ziplist-value
- 保存的键值对数量小于512个;此值可改:hash-max-ziplist-entries
7.4 集合对象
intset使用整数集合作为底层实现;
hashtable使用字典作为底层实现;
7.4.1 编码转换
使用intset需满足:
- 集合对象所包含的所有元素都是整数;
- 集合对象所包含的元素数量不超过512个;此值可改:set-max-ziplist-entries
7.5 有序集合对象
7.5.1 ziplist编码的有序结合对象
对象使用压缩列表作为底层实现,每个集合元素使用两个挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值.
7.5.2 skiplist编码的有序结合对象
skiplist对象使用zset结构作为底层实现,之中同时包含一个字典和跳跃表;
1 typedef struct zset { 2 zskiplist *zsl; 3 dict *dict; 4 } zset;
zsl跳跃表按分值从小到大保存了所有集合元素,通过跳跃表,可以对有序结合进行范围操作,比如zrank,zrange;
dict字典保存了从成员到分值的映射,可以用O(1)复杂度查询给定成员的分值,比如zscore.
注意:zsl和dict会通过指针来共享相同元素的成员和分值,不会产生任何重复成员或分值,不会因此产生额外的内存.
7.5.3 编码的转换
使用ziplist需满足:
- 有序集合保存的元素数量小于128个;此值可改:zset-max-ziplist-entries
- 有序集合保存的所有成员的长度都小于64位;此值可改:zset-max-ziplist-value