struct

sds结构
sds主要是用来存储字符串

struct sdshdr {
    // buf 中已占用空间的长度
    int len;
    // buf 中剩余可用空间的长度
    int free;
    // 数据空间
    char buf[];
};

空间预分配

/*
 * 对 sds 中 buf 的长度进行扩展,确保在函数执行之后,
 * buf 至少会有 addlen + 1 长度的空余空间
 * (额外的 1 字节是为 \0 准备的)
 *
 * 返回值
 *  sds :扩展成功返回扩展后的 sds
 *        扩展失败返回 NULL
 *
 * 复杂度
 *  T = O(N)
 */
sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;
    // 获取 s 目前的空余空间长度
    size_t free = sdsavail(s);
    size_t len, newlen;
    // s 目前的空余空间已经足够,无须再进行扩展,直接返回
    if (free >= addlen) return s;
    // 获取 s 目前已占用空间的长度
    len = sdslen(s);
    sh = (void*)(s - (sizeof(struct sdshdr)));
    // s 最少需要的长度
    newlen = (len + addlen);
    // 根据新长度,为 s 分配新空间所需的大小
    if (newlen < SDS_MAX_PREALLOC)
        // 如果新长度小于 SDS_MAX_PREALLOC (1M)
        // 那么为它分配两倍于所需长度的空间
        newlen *= 2;
    else
        // 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC
        newlen += SDS_MAX_PREALLOC;
    // T = O(N)
    newsh = zrealloc(sh, sizeof(struct sdshdr) + newlen + 1);

    // 内存不足,分配失败,返回
    if (newsh == NULL) return NULL;

    // 更新 sds 的空余长度
    newsh->free = newlen - len;

    // 返回 sds
    return newsh->buf;
}

删除空间

/*
 * 回收 sds 中的空闲空间,
 * 回收不会对 sds 中保存的字符串内容做任何修改。
 *
 * 返回值
 *  sds :内存调整后的 sds
 *
 * 复杂度
 *  T = O(N)
 */
sds sdsRemoveFreeSpace(sds s) {
    struct sdshdr *sh;

    sh = (void*)(s - (sizeof(struct sdshdr)));

    // 进行内存重分配,让 buf 的长度仅仅足够保存字符串内容
    // T = O(N)
    sh = zrealloc(sh, sizeof(struct sdshdr) + sh->len + 1);

    // 空余空间为 0
    sh->free = 0;

    return sh->buf;
}



链表

typedef struct listNode {

    // 前置节点
    struct listNode *prev;

    // 后置节点
    struct listNode *next;

    // 节点的值
    void *value;

} listNode;

链表里面封装了一个迭代器

//
// listIter 双端链表迭代器
//
typedef struct listIter {

    // 当前迭代到的节点
    listNode *next;

    // 迭代的方向
    int direction;

} listIter;



字典

//哈希表结点
typedef struct dictEntry {
    // 键
    void *key;

    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

    // 指向下个哈希表节点,形成链表, 哈希冲突解决的一种方法
    struct dictEntry *next;

} dictEntry;

//
// dictht 哈希表
//每个字典都使用两个哈希表,从而实现渐进式 rehash
// 
typedef struct dictht { // 这是字典的头部

    // 哈希表数组, 每个元素都是一条链表
    dictEntry **table;

    // 哈希表大小
    unsigned long size;              //初始值为4, 跟STL不太一样

    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;

    // 该哈希表已有节点的数量
    unsigned long used;

} dictht;

//
// dict 字典
//
typedef struct dict {
    // 类型特定函数
    dictType *type; // type里面主要记录了一系列的函数,可以说是规定了一系列的接口

    // 私有数据
    void *privdata; // privdata保存了需要传递给那些类型特定函数的可选参数

    // 哈希表
    dictht ht[2]; // 有两张hash表

    // rehash 索引
    // 并没有rehash时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */

    int iterators; // 目前正在运行的安全迭代器的数量

} dict;

哈希算法

/* Thomas Wang's 32 bit Mix Function */
unsigned int dictIntHashFunction(unsigned int key) {
    key += ~(key << 15);
    key ^= (key >> 10);
    key += (key << 3);
    key ^= (key >> 6);
    key += ~(key << 11);
    key ^= (key >> 16);
    return key;
}

/* MurmurHash2, by Austin Appleby
 * Note - This code makes a few assumptions about how your machine behaves -
 * 1. We can read a 4-byte value from any address without crashing
 * 2. sizeof(int) == 4
 *
 * And it has a few limitations -
 *
 * 1. It will not work incrementally.
 * 2. It will not produce the same results on little-endian and big-endian
 *    machines.
 */
unsigned int dictGenHashFunction(const void *key, int len) {
    /* 'm' and 'r' are mixing constants generated offline.
    They're not really 'magic', they just happen to work well.  */
    uint32_t seed = dict_hash_function_seed;
    const uint32_t m = 0x5bd1e995;
    const int r = 24;

    /* Initialize the hash to a 'random' value */
    uint32_t h = seed ^ len;

    /* Mix 4 bytes at a time into the hash */
    const unsigned char *data = (const unsigned char *)key;

    while (len >= 4) {
        uint32_t k = *(uint32_t*)data;

        k *= m;
        k ^= k >> r;
        k *= m;

        h *= m;
        h ^= k;

        data += 4;
        len -= 4;
    }

    /* Handle the last few bytes of the input array  */
    switch (len) {
    case 3: h ^= data[2] << 16;
    case 2: h ^= data[1] << 8;
    case 1: h ^= data[0]; h *= m;
    };

    /* Do a few final mixes of the hash to ensure the last few
    * bytes are well-incorporated. */
    h ^= h >> 13;
    h *= m;
    h ^= h >> 15;

    return (unsigned int)h;
}

/* And a case insensitive hash function (based on djb hash) */
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len) {
    unsigned int hash = (unsigned int)dict_hash_function_seed;

    while (len--)
        hash = ((hash << 5) + hash) + (tolower(*buf++)); /* hash * 33 + c */
    return hash;
}


/* A fingerprintis a 64 bit number that represents the state of the dictionary 
 * at a given time, it's just a few dictproperties xored together. 
 * When an unsafe iterator is initialized, weget the dict fingerprint, and check 
 * the fingerprint again when the iterator isreleased. 
 * If the two fingerprints are different itmeans that the user of the iterator 
 * performed forbidden operations against thedictionary while iterating. */  
long longdictFingerprint(dict *d) {  
    long long integers[6], hash = 0;  
    int j;  
    //将dict结构体中的几个状态放入到数组中,以便后面应用到64 bit MixFunctions中。  
    //dict结构体其实就是一个hash表的实现,而这些状态其实就是第一、第二哈希表的表地址、表大小与  
    //已用条目的数量  
    integers[0] = (long) d->ht[0].table;  
    integers[1] = d->ht[0].size;  
    integers[2] = d->ht[0].used;  
    integers[3] = (long) d->ht[1].table;  
    integers[4] = d->ht[1].size;  
    integers[5] = d->ht[1].used;  
   
    /* We hash N integers by summing everysuccessive integer with the integer 
     * hashing of the previous sum. Basically: 
     * 
     * Result =hash(hash(hash(int1)+int2)+int3) ... 
     * 
     * This way the same set of integers in adifferent order will (likely) hash 
     * to a different number. */  
    //利用64 bit Mix Functions,将这些状态信息混合到hash中,组成最后的指纹,如果这些状态中有一个  
    //出现变化,可以通过一个算法逆推出该状态变化之前的值。例如,d->ht[0].size发生变化,则我们可  
    //以通过hash和其他的几个状态,逆推出d->ht[0].size的最初值。  
    for (j = 0; j < 6; j++) {  
        hash += integers[j];  
        /* For the hashing step we use TomasWang's 64 bit integer hash. */  
        hash = (~hash) + (hash << 21); //hash = (hash << 21) - hash - 1;  
        hash = hash ^ (hash >> 24);  
        hash = (hash + (hash << 3)) +(hash << 8); // hash * 265  
        hash = hash ^ (hash >> 14);  
        hash = (hash + (hash << 2)) +(hash << 4); // hash * 21  
        hash = hash ^ (hash >> 28);  
        hash = hash + (hash << 31);  
    }  
    return hash;  
} 

哈希冲突解决
redis使用的是链地址法, 每个哈希表结点有一个next指针;

哈希表调整
哈希表调整扩展触发条件:

  1. 当服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令, 并且哈希表的负载因子大于等于1。
  2. 服务器目前正在执行BGSAVE命令或者BFREWRITEAOF命令, 并且哈希表的负载因子大于等于5。
    哈希表调整规则由_dictNextPower函数确定, 规则如下:
  3. 如果执行的是扩展操作, 那么ht[1]的大小为第一个大于等于ht[0].used*2的2^n;
  4. 如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2^n;
    下面是迁移函数的实现
int dictRehash(dict *d, int n) {
    // 这里的n代表一共要迁移多少个dictEntry

    // 只可以在 rehash 进行中时执行
    if (!dictIsRehashing(d)) return 0;

    // 进行 N 步迁移
    // T = O(N)
    while (n--) {  
        dictEntry *de, *nextde;

        // 如果 0 号哈希表为空,那么表示 rehash 执行完毕
        // T = O(1)
        if (d->ht[0].used == 0) {
            // 释放 0 号哈希表
            zfree(d->ht[0].table);
            // 将原来的 1 号哈希表设置为新的 0 号哈希表
            d->ht[0] = d->ht[1];
            // 重置旧的 1 号哈希表
            _dictReset(&d->ht[1]);
            // 关闭 rehash 标识
            d->rehashidx = -1;
            // 返回 0 ,向调用者表示 rehash 已经完成
            return 0;
        }

        // Note that rehashidx can't overflow as we are sure there are more
        // elements because ht[0].used != 0
        // 确保 rehashidx 没有越界
        assert(d->ht[0].size > (unsigned)d->rehashidx);

        // 略过数组中为空的索引,找到下一个非空索引
        while (d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++; //dictEntry偏移一位

        // 指向该索引的链表表头节点
        de = d->ht[0].table[d->rehashidx];
        // 将链表中的所有节点迁移到新哈希表
        // T = O(1)
        while (de) {
            unsigned int h;

            // 保存下个节点的指针
            nextde = de->next;

            // 计算新哈希表的哈希值,以及节点插入的索引位置
            h = dictHashKey(d, de->key) & d->ht[1].sizemask;

            // 插入节点到新哈希表,而且是插入到表头
            de->next = d->ht[1].table[h];
            d->ht[1].table[h] = de;

            // 更新计数器
            d->ht[0].used--;
            d->ht[1].used++;

            // 继续处理下个节点
            de = nextde;
        }
        // 将刚迁移完的哈希表索引的指针设为空
        d->ht[0].table[d->rehashidx] = NULL;
        // 更新 rehash 索引
        d->rehashidx++;
    }

    return 1;
}

redis还有一个渐进式rehash方法, 主要解决键值对数量太多迁移时间太长的问题。


压缩列表


整数集合


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 引入 Redis对外提供了5种类型:字符串、列表、集合、有序集合以及哈希表,但底层实现并不是固定的,以上五种数据结...
    宇宙最强架构师阅读 648评论 0 3
  • 数据结构与对象 Redis的底层数据结构,了解Redis的底层数据结构有助于我们更好的运用Redis。 SDS R...
    falm阅读 594评论 0 4
  • 本文将从Redis的基本特性入手,通过讲述Redis的数据结构和主要命令对Redis的基本能力进行直观介绍。之后概...
    kelgon阅读 61,122评论 24 626
  • 在苹果推出iPhone6和iPhone6 Plus后,尽管饱受争议,认为苹果此时推出大屏手机更多的是迎合市场,但也...
    时凤卫阅读 312评论 0 5
  • 时间就像沙漏, 不经意间带走所有。 从来不问我愿不愿意, 从来不在乎我的感受。 于是我学会麻痹自己, 就当做从来都...
    清风唯赏阅读 377评论 2 4