*********************************************************************************************************************
/* 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); 进行检索
}
*********************************************************************************************************************