一、SDS
1、创建SDS
sds sdsnewlen(const void *init, size_t initlen) {
void *sh;
sds s;
char type = sdsReqType(initlen);//根据字符串长度选择不同的类型
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;//SDS_TYPE_5强制转化为SDS_TYPE_8
int hdrlen = sdsHdrSize(type);//计算不同头部所需的长度
unsigned char *fp; /* 指向flags的指针 */
sh = s_malloc(hdrlen+initlen+1);//"+1"是为了结束符'\0'
...
s = (char*)sh+hdrlen;//s是指向buf的指针
fp = ((unsigned char*)s)-1;//s是柔性数组buf的指针,-1即指向flags
...
s[initlen] = '\0';//添加末尾的结束符
return s;
}
创建SDS的大致流程:首先计算好不同类型的头部和初始长度,然后动态分配内存。需要注意以下3点。 1)创建空字符串时,SDS_TYPE_5被强制转换为SDS_TYPE_8。 2)长度计算时有“+1”操作,是为了算上结束符“\0”。 3)返回值是指向sds结构buf字段的指针。
2、拼接字符串
Redis的sds中有如下扩容策略。 1)若sds中剩余空闲长度avail大于新增内容的长度addlen,直接在柔性数组buf末尾追加即可,无须扩容。
2)若sds中剩余空闲长度avail小于或等于新增内容的长度addlen,则分情况讨论:新增后总长度len+addlen<1MB的,按新长度的2倍扩容;新增后总长度len+addlen>1MB的,按新长度加上1MB扩容。
3)最后根据新长度重新选取存储类型,并分配空间。此处若无须更改类型,通过realloc扩大柔性数组即可;否则需要重新开辟内存,并将原字符串的buf内容移动到新位置。
二、跳表
1、一般的跳表结构
2、redis的跳表结构
- 跳跃表由很多层构成。
- 跳跃表有一个头(header)节点,头节点中有一个64层的结构,每层的结构包含指向本层的下个节点的指针,指向本层下个节点中间所跨越的节点个数为本层的跨度(span)。
- 除头节点外,层数最多的节点的层高为跳跃表的高度(level),图3-3中跳跃表的高度为3。
- 每层都是一个有序链表,数据递增。
- 除header节点外,一个元素在上层有序链表中出现,则它一定会在下层有序链表中出现。
- 跳跃表每层最后一个节点指向NULL,表示本层有序链表的结束。
- 跳跃表拥有一个tail指针,指向跳跃表最后一个节点。
- 最底层的有序链表包含所有节点,最底层的节点个数为跳跃表的长度(length)(不包括头节点)。
- 每个节点包含一个后退指针,头节点和第一个节点指向NULL;其他节点指向最底层的前一个节点。
3、redis跳表的应用
-
在Redis中,跳跃表主要应用于有序集合的底层实现(有序集合的另一种实现方式为压缩列表)。
-
Redis的配置文件中关于有序集合底层实现的两个配置。 1)zset-max-ziplist-entries 128:zset采用压缩列表时,元素个数最大值。默认值为128。 2)zset-max-ziplist-value 64:zset采用压缩列表时,每个元素的字符串长度最大值。默认值为64。
-
在创建有序集合时,默认使用压缩列表的底层实现。zset新插入元素时,会判断以下两种条件: ·zset中元素个数大于zset_max_ziplist_entries; ·插入元素的字符串长度大于zset_max_ziplist_value。 当满足任一条件时,Redis便会将zset的底层实现由压缩列表转为跳跃表。zset在转为跳跃表之后,即使元素被逐渐删除,也不会重新转为压缩列表。
-
跳跃表的原理简单,其查询、插入、删除的平均复杂度都为O(logN)。
三、字典
1、redis字典的整体结构
上图(1)描述了字典结构,图(2)描述了扩容的情况。
渐进式rehash
- rehash除了扩容时会触发,缩容时也会触发。Redis整个rehash的实现,主要分为如下几步完成。
- 给Hash表ht[1]申请足够的空间;扩容时空间大小为当前容量2,即d->ht[0].used2;当使用量不到总空间10%时,则进行缩容。缩容时空间大小则为能恰好包含d->ht[0].used个节点的2^N次方幂整数,并把字典中字段rehashidx标识为0。
- 进行rehash操作调用的是dictRehash函数,重新计算ht[0]中每个键的Hash值与索引值(重新计算就叫rehash),依次添加到新的Hash表ht[1],并把老Hash表中该键值对删除。把字典中字段rehashidx字段修改为Hash表ht[0]中正在进行rehash操作节点的索引值。
- rehash操作后,清空ht[0],然后对调一下ht[1]与ht[0]的值,并把字典中rehashidx字段标识为-1。