redis6.0.5之t_hash阅读笔记--哈希

*********************************************************************************************************************
/* Check the length of a number of objects to see if we need to convert a
 * ziplist to a real hash. Note that we only check string encoded objects
 * as their string length can be queried in constant time. */
检查对象的数量,看看是否需要转化zplist为真正的哈希键值对。注意我们这里值检查字符串编码的对象,
因为字符串长度可以在常量时间内查询
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
    int i;

    if (o->encoding != OBJ_ENCODING_ZIPLIST) return; 非ziplist编码,直接返回
    
--  #define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
--  createSizeTConfig("hash-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),

    for (i = start; i <= end; i++) {
        if (sdsEncodedObject(argv[i]) &&
            sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)  当前环境初始值是64个
        {
            hashTypeConvert(o, OBJ_ENCODING_HT); 转化为hash表形式编码
            break;
        }
    }
}
*********************************************************************************************************************
/* Get the value from a ziplist encoded hash, identified by field.
 * Returns -1 when the field cannot be found. */
 从ziplist编码的hash中获取值,通过域标识,当域不能找到时,返回-1
int hashTypeGetFromZiplist(robj *o, sds field,
                           unsigned char **vstr,
                           unsigned int *vlen,
                           long long *vll)
{
    unsigned char *zl, *fptr = NULL, *vptr = NULL;
    int ret;

    serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST);

    zl = o->ptr;
    fptr = ziplistIndex(zl, ZIPLIST_HEAD);
    if (fptr != NULL) {
        fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1); 查找是否存在键为传入参数field的值
        if (fptr != NULL) { 找到了
            /* Grab pointer to the value (fptr points to the field) */ 获取指向值的指针(fptr指向域)
            vptr = ziplistNext(zl, fptr);获取指向值的指针
            serverAssert(vptr != NULL); 确认不为空
        }
    }

    if (vptr != NULL) {
        ret = ziplistGet(vptr, vstr, vlen, vll); 获取具体的值
        serverAssert(ret);
        return 0;
    }

    return -1;
}
*********************************************************************************************************************
/* Get the value from a hash table encoded hash, identified by field.
 * Returns NULL when the field cannot be found, otherwise the SDS value
 * is returned. */
  从hash表中获取值,通过域标识,当域不能找到时,返回null,找到就返回一个sds字符串的值
sds hashTypeGetFromHashTable(robj *o, sds field) {
    dictEntry *de;

    serverAssert(o->encoding == OBJ_ENCODING_HT);

    de = dictFind(o->ptr, field); 查找
    if (de == NULL) return NULL;
    return dictGetVal(de); 获取值
}
*********************************************************************************************************************
/* Higher level function of hashTypeGet*() that returns the hash value
 * associated with the specified field. If the field is found C_OK
 * is returned, otherwise C_ERR. The returned object is returned by
 * reference in either *vstr and *vlen if it's returned in string form,
 * or stored in *vll if it's returned as a number.
 *
 * If *vll is populated *vstr is set to NULL, so the caller
 * can always check the function return by checking the return value
 * for C_OK and checking if vll (or vstr) is NULL. */
关于hashTypeGet相关的高层次的函数 ,通过关联的确定域返回hash值。如果域被找到了,那么返回C_OK.
否则返回C_ERR.返回的对象通过引用在*vstr和*vlen中,如果返回是是字符串格式的,或者保存在*vll中,如果返回的是数字
int hashTypeGetValue(robj *o, sds field, unsigned char **vstr, unsigned int *vlen, long long *vll) {
    if (o->encoding == OBJ_ENCODING_ZIPLIST) { ziplist编码
        *vstr = NULL;
        if (hashTypeGetFromZiplist(o, field, vstr, vlen, vll) == 0) 从ziplist获取值
            return C_OK;
    } else if (o->encoding == OBJ_ENCODING_HT) {
        sds value;
        if ((value = hashTypeGetFromHashTable(o, field)) != NULL) { 从hash表中获取值
            *vstr = (unsigned char*) value; 赋值给vstr,同ziplist保持一致
            *vlen = sdslen(value);
            return C_OK;
        }
    } else {
        serverPanic("Unknown hash encoding");
    }
    return C_ERR;
}
*********************************************************************************************************************
/* Like hashTypeGetValue() but returns a Redis object, which is useful for
 * interaction with the hash type outside t_hash.c.
 * The function returns NULL if the field is not found in the hash. Otherwise
 * a newly allocated string object with the value is returned. */
类似函数hashTypeGetValue,但是返回一个redis对象,被用来做和外部t_hash.c交互的hash类型.
函数返回null,如果域不能够在hash表被找到,否则一个新分配的带着值的字符串对象被返回
robj *hashTypeGetValueObject(robj *o, sds field) {
    unsigned char *vstr;
    unsigned int vlen;
    long long vll;

    if (hashTypeGetValue(o,field,&vstr,&vlen,&vll) == C_ERR) return NULL; 获取值
    if (vstr) return createStringObject((char*)vstr,vlen); 如果是字符串,创建字符串对象
    else return createStringObjectFromLongLong(vll); 如果是数值,也创建字符串对象
}
*********************************************************************************************************************
/* Higher level function using hashTypeGet*() to return the length of the
 * object associated with the requested field, or 0 if the field does not
 * exist. */
使用hashTypeGet*组装的高层次函数,返回请求域所联系的对象的长度,或者返回0如果域不存在
size_t hashTypeGetValueLength(robj *o, sds field) {
    size_t len = 0;
    if (o->encoding == OBJ_ENCODING_ZIPLIST) { ziplist编码情况下
        unsigned char *vstr = NULL;
        unsigned int vlen = UINT_MAX;
        long long vll = LLONG_MAX;

        if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0)
            len = vstr ? vlen : sdigits10(vll); 非空获取长度
    } else if (o->encoding == OBJ_ENCODING_HT) { hash表编码情况下
        sds aux;

        if ((aux = hashTypeGetFromHashTable(o, field)) != NULL)
            len = sdslen(aux); 获取字符串长度
    } else {
        serverPanic("Unknown hash encoding");
    }
    return len;
}
*********************************************************************************************************************
/* Test if the specified field exists in the given hash. Returns 1 if the field
 * exists, and 0 when it doesn't. */
测试特定域是否在给定的hash中,返回1如果域存在,不存在就返回0
int hashTypeExists(robj *o, sds field) {
    if (o->encoding == OBJ_ENCODING_ZIPLIST) {  在ziplist编码的情况下
        unsigned char *vstr = NULL;
        unsigned int vlen = UINT_MAX;
        long long vll = LLONG_MAX;

        if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1; 存在返回1
    } else if (o->encoding == OBJ_ENCODING_HT) { 在hash表情况下
        if (hashTypeGetFromHashTable(o, field) != NULL) return 1; 存在返回1
    } else {
        serverPanic("Unknown hash encoding");
    }
    return 0; 不存在的情况下返回0
}
*********************************************************************************************************************
/* Add a new field, overwrite the old with the new value if it already exists.
 * Return 0 on insert and 1 on update.
增加一个新的域,如果已经存在就用新值覆盖存在的旧值。当插入新值是返回0, 如果是更新返回1
 * By default, the key and value SDS strings are copied if needed, so the
 * caller retains ownership of the strings passed. However this behavior
 * can be effected by passing appropriate flags (possibly bitwise OR-ed):
默认情况下,如果需要,键和值的sds字符串被拷贝。这样调用者就可以保留传入字符串的所有权。
然而这个现象能够被通过传入适当的标志影响(可以能按照位异或操作)
 * HASH_SET_TAKE_FIELD -- The SDS field ownership passes to the function. sds域的所有权传入函数中
 * HASH_SET_TAKE_VALUE -- The SDS value ownership passes to the function. sds值的所有权传入函数中
 *
 * When the flags are used the caller does not need to release the passed
 * SDS string(s). It's up to the function to use the string to create a new
 * entry or to free the SDS string before returning to the caller.
当传入的标志被使用,调用者不需要去释放传入的sds字符串。函数可以使用这个字符串去创建一个新的实体
或者在函数返回调用者之前释放sds字符串
 * HASH_SET_COPY corresponds to no flags passed, and means the default
 * semantics of copying the values if needed.
 HASH_SET_COPY对应没有传入任何标志,这个种情况意味着默认的需要复制的值
 */
#define HASH_SET_TAKE_FIELD (1<<0) 1
#define HASH_SET_TAKE_VALUE (1<<1) 2
#define HASH_SET_COPY 0  
int hashTypeSet(robj *o, sds field, sds value, int flags) {
    int update = 0;

    if (o->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *zl, *fptr, *vptr;

        zl = o->ptr;
        fptr = ziplistIndex(zl, ZIPLIST_HEAD); 指向ziplist头部
        if (fptr != NULL) {
            fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1); 查找对应域是否存在
            if (fptr != NULL) {
                /* Grab pointer to the value (fptr points to the field) */ 获取指向域的指针
                vptr = ziplistNext(zl, fptr); 获取对应的值
                serverAssert(vptr != NULL);
                update = 1;  已经存在的情况下,是更新

                /* Delete value */ 删除旧值
                zl = ziplistDelete(zl, &vptr); 

                /* Insert new value */ 插入新值
                zl = ziplistInsert(zl, vptr, (unsigned char*)value,
                        sdslen(value));
            }
        }

        if (!update) {  如果不是更新,那么说明原来的ziplist中不存在
            /* Push new field/value pair onto the tail of the ziplist */ 添加新的域/值对到原理ziplist的末尾
            zl = ziplistPush(zl, (unsigned char*)field, sdslen(field),
                    ZIPLIST_TAIL); 添加域(即键)
            zl = ziplistPush(zl, (unsigned char*)value, sdslen(value),
                    ZIPLIST_TAIL); 添加值
        }
        o->ptr = zl; 重新指向新的ziplist

        /* Check if the ziplist needs to be converted to a hash table */ 测试新的ziplist是否需要转化为一个hash表
        if (hashTypeLength(o) > server.hash_max_ziplist_entries)
            hashTypeConvert(o, OBJ_ENCODING_HT);
    } else if (o->encoding == OBJ_ENCODING_HT) { 如果是hash表的情况
        dictEntry *de = dictFind(o->ptr,field); 先查找
        if (de) { 存在的情况下
            sdsfree(dictGetVal(de)); 释放原先的旧值
            if (flags & HASH_SET_TAKE_VALUE) {
                dictGetVal(de) = value;  更新成新值
                value = NULL; 值释放
            } else {
                dictGetVal(de) = sdsdup(value);  复制创建一个新的字符串,传入的值不动
            }
            update = 1;  更新
        } else {
            sds f,v;
            if (flags & HASH_SET_TAKE_FIELD) { 释放传入的域
                f = field;
                field = NULL;
            } else {
                f = sdsdup(field);  不释放的情况,需要创建一个新的键
            }
            if (flags & HASH_SET_TAKE_VALUE) { 释放传入的值
                v = value;
                value = NULL;
            } else {
                v = sdsdup(value);
            }
            dictAdd(o->ptr,f,v); 增加新的键值对
        }
    } else {
        serverPanic("Unknown hash encoding");
    }

    /* Free SDS strings we did not referenced elsewhere if the flags
     * want this function to be responsible. */
     如果标志想要这个函数内部处理传入的非空参数,那么这里需要释放这些传入的sds参数
    if (flags & HASH_SET_TAKE_FIELD && field) sdsfree(field); 
    if (flags & HASH_SET_TAKE_VALUE && value) sdsfree(value);
    return update;  返回更新标志
}
*********************************************************************************************************************
/* Delete an element from a hash.
 * Return 1 on deleted and 0 on not found. */
从hash中删除一个元素。返回1如果删除,返回0如果找不到
int hashTypeDelete(robj *o, sds field) {
    int deleted = 0;

    if (o->encoding == OBJ_ENCODING_ZIPLIST) { ziplist编码
        unsigned char *zl, *fptr;

        zl = o->ptr;
        fptr = ziplistIndex(zl, ZIPLIST_HEAD); 定位到头部
        if (fptr != NULL) {
            fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1); 查找传入的域(键)
            if (fptr != NULL) { 找到就删除
                zl = ziplistDelete(zl,&fptr); /* Delete the key. */  删除键
                zl = ziplistDelete(zl,&fptr); /* Delete the value. */ 删除值
                o->ptr = zl;
                deleted = 1; 删除标志设置为1
            }
        }
    } else if (o->encoding == OBJ_ENCODING_HT) {  hash表的情况下
        if (dictDelete((dict*)o->ptr, field) == C_OK) {  删除键
            deleted = 1; 

            /* Always check if the dictionary needs a resize after a delete. */ 是否需要重新组织内存空间
            if (htNeedsResize(o->ptr)) dictResize(o->ptr);
        }

    } else {
        serverPanic("Unknown hash encoding");
    }
    return deleted;
}
*********************************************************************************************************************
/* Return the number of elements in a hash. */
返回hash表中元素的数量
unsigned long hashTypeLength(const robj *o) {
    unsigned long length = ULONG_MAX;

    if (o->encoding == OBJ_ENCODING_ZIPLIST) {
        length = ziplistLen(o->ptr) / 2;  总长度除以1就是对数
    } else if (o->encoding == OBJ_ENCODING_HT) {
        length = dictSize((const dict*)o->ptr); 这里直接返回对数
    } else {
        serverPanic("Unknown hash encoding");
    }
    return length;
}
*********************************************************************************************************************
初始化hash迭代器
hashTypeIterator *hashTypeInitIterator(robj *subject) {
    hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator)); 先分配所需空间
    hi->subject = subject;
    hi->encoding = subject->encoding;

    if (hi->encoding == OBJ_ENCODING_ZIPLIST) { ziplist编码
        hi->fptr = NULL;
        hi->vptr = NULL;
    } else if (hi->encoding == OBJ_ENCODING_HT) { hash表
        hi->di = dictGetIterator(subject->ptr); 获取迭代器
    } else {
        serverPanic("Unknown hash encoding");
    }
    return hi;
}
*********************************************************************************************************************
释放迭代器
void hashTypeReleaseIterator(hashTypeIterator *hi) {
    if (hi->encoding == OBJ_ENCODING_HT)
        dictReleaseIterator(hi->di);
    zfree(hi);
}
*********************************************************************************************************************
/* Move to the next entry in the hash. Return C_OK when the next entry
 * could be found and C_ERR when the iterator reaches the end. */
移动到下一个hash中的实体。如果下个实体可以找到那么返回C_OK,如果迭代到达结尾返回C_ERR
int hashTypeNext(hashTypeIterator *hi) {
    if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *zl;
        unsigned char *fptr, *vptr;

        zl = hi->subject->ptr;
        fptr = hi->fptr;
        vptr = hi->vptr;

        if (fptr == NULL) { 如果迭代器是空,指向头部
            /* Initialize cursor */ 初始化游标
            serverAssert(vptr == NULL);
            fptr = ziplistIndex(zl, 0);
        } else {
            /* Advance cursor */  向前的光标
            serverAssert(vptr != NULL);
            fptr = ziplistNext(zl, vptr); 指向下一个
        }
        if (fptr == NULL) return C_ERR;

        /* Grab pointer to the value (fptr points to the field) */ 从指针获取值
        vptr = ziplistNext(zl, fptr);
        serverAssert(vptr != NULL);

        /* fptr, vptr now point to the first or next pair */ fptr ,vptr 现在指向第一个或者下一个键值对
        hi->fptr = fptr;
        hi->vptr = vptr;
    } else if (hi->encoding == OBJ_ENCODING_HT) { hash表的情况
        if ((hi->de = dictNext(hi->di)) == NULL) return C_ERR;
    } else {
        serverPanic("Unknown hash encoding");
    }
    return C_OK;
}
*********************************************************************************************************************
/* Get the field or value at iterator cursor, for an iterator on a hash value
 * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */
获取迭代游标所在的键和值,对一个用ziplist编码的hash值上的迭代器。
原型类似于函数hashTypeGetFromZiplist
void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what,
                                unsigned char **vstr,
                                unsigned int *vlen,
                                long long *vll)
{
    int ret;

    serverAssert(hi->encoding == OBJ_ENCODING_ZIPLIST); 确认是ziplist编码

    if (what & OBJ_HASH_KEY) { 获取键
        ret = ziplistGet(hi->fptr, vstr, vlen, vll);
        serverAssert(ret);
    } else { 获取值
        ret = ziplistGet(hi->vptr, vstr, vlen, vll);
        serverAssert(ret);
    }
}
*********************************************************************************************************************
/* Get the field or value at iterator cursor, for an iterator on a hash value
 * encoded as a hash table. Prototype is similar to
 * `hashTypeGetFromHashTable`. */
在迭代器的游标中获取键或值,对于一个hash表编码的在hash值上的迭代器,原型类似于函数hashTypeGetFromHashTable
sds hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what) {
    serverAssert(hi->encoding == OBJ_ENCODING_HT);

    if (what & OBJ_HASH_KEY) {
        return dictGetKey(hi->de); 返回键
    } else {
        return dictGetVal(hi->de); 返回值
    }
}
*********************************************************************************************************************
/* Higher level function of hashTypeCurrent*() that returns the hash value
 * at current iterator position.
hashTypeCurrent这类函数的高层次的表现,返回当前迭代器位置的hash值
 * The returned element is returned by reference in either *vstr and *vlen if
 * it's returned in string form, or stored in *vll if it's returned as
 * a number.
返回的元素,如果是字符串形式,通过 *vstr 或者 *vlen的引用返回,如果是数值,通过保存在*vll中返回
 * If *vll is populated *vstr is set to NULL, so the caller
 * can always check the function return by checking the return value
 * type checking if vstr == NULL. 
 如果*vll被使用了,那么*vstr就会被设置为null,这样调用者总是可以通过检查条件vstr == NULL,来判断返回的值是数值还是字符串
 */
void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) {
    if (hi->encoding == OBJ_ENCODING_ZIPLIST) {ziplist编码
        *vstr = NULL;
        hashTypeCurrentFromZiplist(hi, what, vstr, vlen, vll);  返回当前指向的值
    } else if (hi->encoding == OBJ_ENCODING_HT) {
        sds ele = hashTypeCurrentFromHashTable(hi, what); 当前指向的元素
        *vstr = (unsigned char*) ele; 值
        *vlen = sdslen(ele); 长度
    } else {
        serverPanic("Unknown hash encoding");
    }
}
*********************************************************************************************************************
/* Return the key or value at the current iterator position as a new
 * SDS string. */
用一个新sds字符串返回当前迭代器位置的键或值
sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what) {
    unsigned char *vstr;
    unsigned int vlen;
    long long vll;

    hashTypeCurrentObject(hi,what,&vstr,&vlen,&vll); 返回当前迭代器位置的元素
    if (vstr) return sdsnewlen(vstr,vlen); 字符串
    return sdsfromlonglong(vll);数值
}
*********************************************************************************************************************
robj *hashTypeLookupWriteOrCreate(client *c, robj *key) {
    robj *o = lookupKeyWrite(c->db,key);  库中查找键
    if (o == NULL) { 找不到
        o = createHashObject(); 创建新对象
        dbAdd(c->db,key,o); 增加新对象
    } else {
        if (o->type != OBJ_HASH) {  类型不是hash,提示错误
            addReply(c,shared.wrongtypeerr);
            return NULL;
        }
    }
    return o;
}
*********************************************************************************************************************
从ziplist转为hashtype
void hashTypeConvertZiplist(robj *o, int enc) {
    serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST); 先确认是ziplist编码

    if (enc == OBJ_ENCODING_ZIPLIST) { 如果要编码的目标也是ziplist,无需转化
        /* Nothing to do... */

    } else if (enc == OBJ_ENCODING_HT) {  如果编码目标是hash
        hashTypeIterator *hi;
        dict *dict;
        int ret;

        hi = hashTypeInitIterator(o);    创建ziplist的迭代器
        dict = dictCreate(&hashDictType, NULL); 创建新的hash

        while (hashTypeNext(hi) != C_ERR) {  寻找下一个元素
            sds key, value;

            key = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY); 返回字符串键
            value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE); 返回字符串键对应的值
            ret = dictAdd(dict, key, value);加入到hash列表中
            if (ret != DICT_OK) { 添加不成功,说明解析ziplist出现问题
                serverLogHexDump(LL_WARNING,"ziplist with dup elements dump",
                    o->ptr,ziplistBlobLen(o->ptr));
                serverPanic("Ziplist corruption detected");
            }
        }
        hashTypeReleaseIterator(hi); 释放迭代器
        zfree(o->ptr); 释放原先指向的内容
        o->encoding = OBJ_ENCODING_HT; 更新编码类型
        o->ptr = dict;指向新的hash
    } else {
        serverPanic("Unknown hash encoding");
    }
}
*********************************************************************************************************************
转化为hash类型
void hashTypeConvert(robj *o, int enc) {
    if (o->encoding == OBJ_ENCODING_ZIPLIST) { 只有当原编码是ziplist时,才有可以转hash
        hashTypeConvertZiplist(o, enc);
    } else if (o->encoding == OBJ_ENCODING_HT) { hash转ziplist没有实现
        serverPanic("Not implemented");
    } else {
        serverPanic("Unknown hash encoding");
    }
}
*********************************************************************************************************************
/*-----------------------------------------------------------------------------
 * Hash type commands hash类型的命令
 *----------------------------------------------------------------------------*/
Redis Hsetnx 命令用于为哈希表中不存在的的字段赋值
如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。
如果字段已经存在于哈希表中,操作无效。
如果 key 不存在,一个新哈希表被创建并执行 HSETNX 命令
hsetnx myhash f1 test1
void hsetnxCommand(client *c) {
    robj *o;
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 如果库中不存在该键,那么新建hash表,并且添加新的键值对
    hashTypeTryConversion(o,c->argv,2,3); 存在的情况,是否需要转化编码类型

    if (hashTypeExists(o, c->argv[2]->ptr)) { 键值已经在hash表中存在,直接返回0
        addReply(c, shared.czero);
    } else {
        hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY); 在hash表中添加新的键值对
        addReply(c, shared.cone); 返回1
        signalModifiedKey(c,c->db,c->argv[1]); 发出键值被修改的消息
        notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id); 通知订阅的客户端hset消息
        server.dirty++; 变化的键加1
    }
}
*********************************************************************************************************************
Redis Hset 命令用于为哈希表中的字段赋值 。
如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。
如果字段已经存在于哈希表中,旧值将被覆盖
hset myhash f1 test2

void hsetCommand(client *c) {
    int i, created = 0;
    robj *o;

    if ((c->argc % 2) == 1) {  如果传入的参数不是成对的,那么传入参数有问题
        addReplyError(c,"wrong number of arguments for HMSET");
        return;
    }

    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 如果库中不存在该键,那么新建hash表,并且添加新的键值对
    hashTypeTryConversion(o,c->argv,2,c->argc-1); 存在的情况,是否需要转化编码类型

    for (i = 2; i < c->argc; i += 2) 对每一对键值对进行检查
        created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);

    /* HMSET (deprecated) and HSET return value is different. */ HMSET(已弃用)和HSET返回值不同
    char *cmdname = c->argv[0]->ptr;
    if (cmdname[1] == 's' || cmdname[1] == 'S') { 通过字母判断是否是多键值设置
        /* HSET */hset命令
        addReplyLongLong(c, created);
    } else {
        /* HMSET */hmset命令
        addReply(c, shared.ok);
    }
    signalModifiedKey(c,c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
    server.dirty++;
}
*********************************************************************************************************************
Redis Hincrby 命令用于为哈希表中的字段值加上指定增量值。
增量也可以为负数,相当于对指定字段进行减法操作。
如果哈希表的 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。
如果指定的字段不存在,那么在执行命令前,字段的值被初始化为 0 。
对一个储存字符串值的字段执行 HINCRBY 命令将造成一个错误。
本操作的值被限制在 64 位(bit)有符号数字表示之内
HSET myhash f2 5
HINCRBY myhash f2 2

void hincrbyCommand(client *c) {
    long long value, incr, oldvalue;
    robj *o;
    sds new;
    unsigned char *vstr;
    unsigned int vlen;

    if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return; 传入要增加的非整型值,返回错误
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;  如果库中不存在该键,那么新建hash表,并且添加新的键值对
    if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&value) == C_OK) { 获取对应键的值
        if (vstr) { 如果是字符串
            if (string2ll((char*)vstr,vlen,&value) == 0) { 将字符串转化为数字
                addReplyError(c,"hash value is not an integer"); 不成功,那就是不是一个整型
                return;
            }
        } /* Else hashTypeGetValue() already stored it into &value */ 非字符串的情况下,数值已经存储到value中
    } else {
        value = 0; 不在库中,那么初始化为0
    }

    oldvalue = value; 保存旧值
    if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
        (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {  超出最小最大值的范围,报错
        addReplyError(c,"increment or decrement would overflow");
        return;
    }
    value += incr; 正常情况,将传入值进行计算
    new = sdsfromlonglong(value); 转化为字符串输出
    hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE); 将新值设置到hash中,如果不存在就新建键值对(需要处理传入的字符串)
    addReplyLongLong(c,value); 回复客户端
    signalModifiedKey(c,c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
    server.dirty++;
}
*********************************************************************************************************************
Redis Hincrbyfloat 命令用于为哈希表中的字段值加上指定浮点数增量值。
如果指定的字段不存在,那么在执行命令前,字段的值被初始化为 0
// HSET mykey f3 10.50
HINCRBYFLOAT mykey f3 0.1

void hincrbyfloatCommand(client *c) {
    long double value, incr;
    long long ll;
    robj *o;
    sds new;
    unsigned char *vstr;
    unsigned int vlen;

    if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;  传入参数是否是数值,不是就返回
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 如果库中不存在该键,那么新建hash表,并且添加新的键值对
    if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&ll) == C_OK) { 存在获取对应键值
        if (vstr) { 是字符串
            if (string2ld((char*)vstr,vlen,&value) == 0) { 转浮点型数据
                addReplyError(c,"hash value is not a float"); 不能转提示错误
                return;
            }
        } else {
            value = (long double)ll;  进行类型转化
        }
    } else {
        value = 0; 不存在初始化为0
    }

    value += incr;
    if (isnan(value) || isinf(value)) { 无穷大和空,不能相加
        addReplyError(c,"increment would produce NaN or Infinity");
        return;
    }

/* The maximum number of characters needed to represent a long double
 * as a string (long double has a huge range).
 * This should be the size of the buffer given to ld2string */
-- #define MAX_LONG_DOUBLE_CHARS 5*1024

    char buf[MAX_LONG_DOUBLE_CHARS]; 
    int len = ld2string(buf,sizeof(buf),value,LD_STR_HUMAN);  浮点型转字符串得到的长度
    new = sdsnewlen(buf,len);
    hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE); 设置新的键值对,传入的参数需要内部处理
    addReplyBulkCBuffer(c,buf,len);
    signalModifiedKey(c,c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id);
    server.dirty++;

    /* Always replicate HINCRBYFLOAT as an HSET command with the final value
     * in order to make sure that differences in float pricision or formatting
     * will not create differences in replicas or after an AOF restart. */
     始终将命令HINCRBYFLOAT赋值成为具有最终hset值的命令。
     这是为了确保不同的操作中(复制或者在AOF重启之后)浮点数精度和格式保持一致。
    robj *aux, *newobj;
    aux = createStringObject("HSET",4);
    newobj = createRawStringObject(buf,len);
    rewriteClientCommandArgument(c,0,aux);
    decrRefCount(aux);
    rewriteClientCommandArgument(c,3,newobj);
    decrRefCount(newobj);
}
*********************************************************************************************************************

static void addHashFieldToReply(client *c, robj *o, sds field) {
    int ret;

    if (o == NULL) {  如果对象为空,返回空
        addReplyNull(c);
        return;
    }

    if (o->encoding == OBJ_ENCODING_ZIPLIST) { ziplist类型编码
        unsigned char *vstr = NULL;
        unsigned int vlen = UINT_MAX;
        long long vll = LLONG_MAX;

        ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll); 从ziplist根据传入键field获取值
        if (ret < 0) { 不存在
            addReplyNull(c);
        } else {
            if (vstr) { 是字符串
                addReplyBulkCBuffer(c, vstr, vlen);
            } else { 非字符串,数值
                addReplyBulkLongLong(c, vll);
            }
        }

    } else if (o->encoding == OBJ_ENCODING_HT) { hash类型的表
        sds value = hashTypeGetFromHashTable(o, field); 从hash表中获取值
        if (value == NULL)
            addReplyNull(c);
        else
            addReplyBulkCBuffer(c, value, sdslen(value));
    } else {
        serverPanic("Unknown hash encoding");
    }
}
*********************************************************************************************************************
Redis Hget 命令用于返回哈希表中指定字段的值
void hgetCommand(client *c) {
    robj *o;

    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL ||  如果库中不存在
        checkType(c,o,OBJ_HASH)) return;  或者类型不是hash,那么直接返回即可

    addHashFieldToReply(c, o, c->argv[2]->ptr); 否则返回查找的结果
}
*********************************************************************************************************************
Redis Hmget 命令用于返回哈希表中,一个或多个给定字段的值。
如果指定的字段不存在于哈希表,那么返回一个 nil 值
HMGET myhash f1 f2 f3
void hmgetCommand(client *c) {
    robj *o;
    int i;

    /* Don't abort when the key cannot be found. Non-existing keys are empty
     * hashes, where HMGET should respond with a series of null bulks. */
     当键在库中找不到时也不终止。不存在的键都是空的hash值,HMGET应当返回一系列的空值
    o = lookupKeyRead(c->db, c->argv[1]); hash表是否存在库中
    if (o != NULL && o->type != OBJ_HASH) { 存在hash表,但是不是hash类型
        addReply(c, shared.wrongtypeerr);
        return;
    }

    addReplyArrayLen(c, c->argc-2); 增加回复客户端的数组长度
    for (i = 2; i < c->argc; i++) { 对每个键遍历
        addHashFieldToReply(c, o, c->argv[i]->ptr); 将回复添加到数组对应的位置上
    }
}
*********************************************************************************************************************
Redis Hdel 命令用于删除哈希表 key 中的一个或多个指定字段,不存在的字段将被忽略
HDEL myhash  f1

void hdelCommand(client *c) {
    robj *o;
    int j, deleted = 0, keyremoved = 0;

    if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || 如果库中不存在
        checkType(c,o,OBJ_HASH)) return;   或者类型不是hash,直接返回

    for (j = 2; j < c->argc; j++) {
        if (hashTypeDelete(o,c->argv[j]->ptr)) { 从hash表中删除一个键
            deleted++;
            if (hashTypeLength(o) == 0) { 如果hash已空,那么从库中删除这个hash表
                dbDelete(c->db,c->argv[1]);
                keyremoved = 1;
                break;
            }
        }
    }
    if (deleted) { 如果有删除
        signalModifiedKey(c,c->db,c->argv[1]); 通知键修改的信息
        notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id); 通知订阅的客户端hdel消息
        if (keyremoved) 如果hash表被清除
            notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1], 通知客户端del消息
                                c->db->id);
        server.dirty += deleted; 删除键的个数
    }
    addReplyLongLong(c,deleted); 回复客户端删除的个数
}
*********************************************************************************************************************
Redis Hlen 命令用于获取哈希表中字段的数量
hlen myhash

void hlenCommand(client *c) {
    robj *o;

    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||  如果库中不存在
        checkType(c,o,OBJ_HASH)) return;  或者类型不是hash,直接返回

    addReplyLongLong(c,hashTypeLength(o)); 返回长度
}
*********************************************************************************************************************
Redis HSTRLEN 命令用于获取哈希表中字段的数量
HSTRLEN myhash f1

void hstrlenCommand(client *c) {
    robj *o;

    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,o,OBJ_HASH)) return;
    addReplyLongLong(c,hashTypeGetValueLength(o,c->argv[2]->ptr)); 返回键对应值的长度
}
*********************************************************************************************************************
获取当前迭代器位置的值返回给客户端
static void addHashIteratorCursorToReply(client *c, hashTypeIterator *hi, int what) {
    if (hi->encoding == OBJ_ENCODING_ZIPLIST) { ziplist编码
        unsigned char *vstr = NULL;
        unsigned int vlen = UINT_MAX;
        long long vll = LLONG_MAX;

        hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); 获取当前位置的键和值
        if (vstr) 字符串
            addReplyBulkCBuffer(c, vstr, vlen);
        else 数值
            addReplyBulkLongLong(c, vll);
    } else if (hi->encoding == OBJ_ENCODING_HT) {  hash编码
        sds value = hashTypeCurrentFromHashTable(hi, what); 当前位置的值
        addReplyBulkCBuffer(c, value, sdslen(value));
    } else {
        serverPanic("Unknown hash encoding");
    }
}
*********************************************************************************************************************
void genericHgetallCommand(client *c, int flags) {
    robj *o;
    hashTypeIterator *hi;
    int length, count = 0;

    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymap[c->resp]))  如果库中不存在
        == NULL || checkType(c,o,OBJ_HASH)) return;  或者类型不是hash,直接返回

    /* We return a map if the user requested keys and values, like in the
     * HGETALL case. Otherwise to use a flat array makes more sense. */
如果用户请求键和值,我们将返回一个map,就像HGETALL的情况一样。否则使用一维数组更有明了
    length = hashTypeLength(o); hash中总的个数
    if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) {  如果键和值都需要,使用马屁形式返回
        addReplyMapLen(c, length);
    } else { 否则使用一维数组
        addReplyArrayLen(c, length); 
    }

    hi = hashTypeInitIterator(o); 初始化迭代器
    while (hashTypeNext(hi) != C_ERR) {  下一个不为空
        if (flags & OBJ_HASH_KEY) {  如果需要键,就添加键
            addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY);
            count++;
        }
        if (flags & OBJ_HASH_VALUE) { 如果需要值,就添加值
            addHashIteratorCursorToReply(c, hi, OBJ_HASH_VALUE);
            count++;
        }
    }

    hashTypeReleaseIterator(hi); 释放迭代器

    /* Make sure we returned the right number of elements. */ 确保我们返回正确的元素个数
    if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) count /= 2;
    serverAssert(count == length);
}
*********************************************************************************************************************
Redis Hkeys 命令用于获取哈希表中的所有域(field)
hkeys myhash

void hkeysCommand(client *c) {
    genericHgetallCommand(c,OBJ_HASH_KEY);
}
*********************************************************************************************************************
Redis Hvals 命令返回哈希表所有的值
HVALS myhash

void hvalsCommand(client *c) {
    genericHgetallCommand(c,OBJ_HASH_VALUE);
}
*********************************************************************************************************************
Redis Hgetall 命令用于返回哈希表中,所有的字段和值。
在返回值里,紧跟每个字段名(field name)之后是字段的值(value),所以返回值的长度是哈希表大小的两倍。
hgetall myhash

void hgetallCommand(client *c) {
    genericHgetallCommand(c,OBJ_HASH_KEY|OBJ_HASH_VALUE);
}
*********************************************************************************************************************
Redis Hexists 命令用于查看哈希表的指定字段是否存在
HEXISTS myhash f1

void hexistsCommand(client *c) {
    robj *o;
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,o,OBJ_HASH)) return;

    addReply(c, hashTypeExists(o,c->argv[2]->ptr) ? shared.cone : shared.czero); hash中是否存在键
}
*********************************************************************************************************************
Redis HSCAN 命令用于迭代哈希表中的键的匹配
HSCAN key cursor [MATCH pattern] [COUNT count]
cursor - 游标。
pattern - 匹配的模式。
count - 指定从数据集里返回多少元素,默认值为 10 

HSCAN sites 0 match "f*"

void hscanCommand(client *c) {
    robj *o;
    unsigned long cursor;

    if (parseScanCursorOrReply(c,c->argv[2],&cursor) == C_ERR) return; 检验游标是否正常
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
        checkType(c,o,OBJ_HASH)) return;
    scanGenericCommand(c,o,cursor); 进行检索
}
*********************************************************************************************************************

 

posted on 2021-07-07 17:24  子虚乌有  阅读(62)  评论(0)    收藏  举报