sds(简单动态字符串)
struct sdshdr {
unsigned int len; //分配到长度
unsigned int free; //未使用的长度
char buf[]; //数据
};
- 扩容
- 如果free的长度够用直接使用
- 如果新串的长度小于SDS_MAX_PREALLOC(1M),扩容后的长度为新串长度的两倍
- 否则扩容后的长度为新串长度加上SDS_MAX_PREALLOC(1M)
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
size_t free = sdsavail(s);
size_t len, newlen;
if (free >= addlen) return s;
len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr)));
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
if (newsh == NULL) return NULL;
newsh->free = newlen - len;
return newsh->buf;
}
- 与c字符串相比的优点
- 常数复杂度获取字符串的长度
- 杜绝缓冲区溢出
- 空间预分配减少修改字符串长度内存重新分配的次数
- 二进制安全
dict(字典)
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table; //哈希表
unsigned long size; //大小
unsigned long sizemask; //size-1 用于计算索引
unsigned long used; //哈希表已有的节点数
} dictht;
哈希冲突
- 解决冲突使用的是链地址法,即出现冲突会将entry放在冲突位置的单链表尾部。 扩容
- 当数据过多哈希表长度又较小时,就会出现很多冲突,导致单链表过长,哈希表的查询性能会急剧下降,故应该在合适的时间对哈希表进行扩容。
- 扩容条件
- 负载因子:哈希表中entry的数目 / 哈希表的长度
- Redis没有正在执行和Fork相关指令时(BGSAVE,BGREWRITEAOF),负载因子大于等于1扩容。
- Redis正在执行和Fork相关指令时(BGSAVE,BGREWRITEAOF),负载因子大于等于5扩容。 为什么不同条件条件扩容的负载因子不一样?
- BGSAVE和BGREWRITEAOF会Fork出当前服务器进程的子进程,并且大多数操作系统采用写时复制技术(copy-on——write)来优化Fork一个子进程的时间并且节约内存,所以在子进程运行期间,提高负载因子,尽可能减少父进程对内存的写入操作,尽可能节约内存。
- 写时复制通俗理解,未采用写时复制技术时,创建一个进程的副本(Fork出一个子进程)会将父进程的内存空间也创建出一个副本给子进程使用,但是这样会浪费内存并且如果父进程空间大Fork的时间也会很长,而采用写时复制技术,Fork一个子进程时不需要创建一个父进程空间的副本,而是子进程和父进程共享内存空间,只有父进程执行写操作时,才会将父进程修改那一页创建一个副本给子进程使用。
- 扩容后的大小
- 扩容后的大小为2^n,2^n为第一个大于等于当前哈希表entry数目*2的数
/* Our hash table capability is a power of two */
static unsigned long _dictNextPower(unsigned long size)
{
unsigned long i = DICT_HT_INITIAL_SIZE;
if (size >= LONG_MAX) return LONG_MAX;
//找到第一个大于等于size的2^n作为扩容后的大小,size=d->ht[0].used*2
while(1) {
if (i >= size)
return i;
i *= 2;
}
}
/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
/* Incremental rehashing already in progress. Return. */
if (dictIsRehashing(d)) return DICT_OK;
/* If the hash table is empty expand it to the initial size. */
if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
/* If we reached the 1:1 ratio, and we are allowed to resize the hash
* table (global setting) or we should avoid it but the ratio between
* elements/buckets is over the "safe" threshold, we resize doubling
* the number of buckets. */
if (d->ht[0].used >= d->ht[0].size &&
(dict_can_resize ||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
{
return dictExpand(d, d->ht[0].used*2); //传过去的size为当前哈希表元素的二倍
}
return DICT_OK;
}
- 缩容
- 负载因子小于0.1进行缩容
- 缩容后的大小为2^n,2^n为第一个大于等于当前哈希表entry数目*2的数 渐进式rehash
- 开始时将rehashidx设置为0,表示rehash正式开始。
- 在rehash期间,每次对字典进行增删改查操作时,除了执行指定操作外,还会将ht[0]的rehashidx桶位上的entry,rehash到ht[1],完成后rehash后,rehashidx加1。
- rehash完成后rehashidx重新设置为-1。 --------待续--------