哈希对象的编码可以是ziplist或者hashtable。
ziplist编码的哈希对象使用压缩列表作为底层实现,当新的键值对加入哈希对象时,先将保存健的压缩列表节点添加到压缩列表表尾,然后再将保存值的压缩列表节点添加到表尾。
hashtable编码的哈希对象使用字典作为底层实现,哈希对象的键值对由字典的键值对来保存。
当哈希对象同时满足两个条件可以使用ziplist编码:
1、哈希对象保存的所有键值对的键和值的字符串长度都是小于64字节;
2、哈希对象的键值对数量小于512;
不能满足这两个条件,哈希对象会将对象的编码转换成hashtable编码。
一、哈希对象的实现
1、哈希对象的操作
获取指定key的哈希对象,不存在,为key新建一个哈希对象返回
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) {
addReply(c,shared.wrongtypeerr);
return NULL;
}
}
return o;
}
添加键值对到哈希对象中
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);
if (fptr != NULL) {
fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1);
//哈希对象中存在这个field,更新value
if (fptr != NULL) {
vptr = ziplistNext(zl, fptr);
update = 1;
zl = ziplistDelete(zl, &vptr);//删除存储旧value的压缩列表节点
zl = ziplistInsert(zl, vptr, (unsigned char*)value,
sdslen(value));//在旧value节点的位置添加新value
}
}
if (!update) {
//哈希对象中不存在这个field,直接将键和值依次添加到压缩列表尾部
zl = ziplistPush(zl, (unsigned char*)field, sdslen(field),
ZIPLIST_TAIL);
zl = ziplistPush(zl, (unsigned char*)value, sdslen(value),
ZIPLIST_TAIL);
}
o->ptr = zl;
//判断哈希对象的键值对数量是否大于512,进行ziplist编码转hashtable编码
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
hashTypeConvert(o, OBJ_ENCODING_HT);
} else if (o->encoding == OBJ_ENCODING_HT) {
dictEntry *de = dictFind(o->ptr,field);
if (de) {
//哈希对象存在field,更新value
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;//哈希对象中不存在field,将field-value添加到字典中
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);
}
} //释放field和value字符串对象
if (flags & HASH_SET_TAKE_FIELD && field) sdsfree(field);
if (flags & HASH_SET_TAKE_VALUE && value) sdsfree(value);
return update;
}
删除hash对象指定field的键值对
int hashTypeDelete(robj *o, sds field) {
int deleted = 0;
if (o->encoding == OBJ_ENCODING_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);
zl = ziplistDelete(zl,&fptr);
o->ptr = zl;
deleted = 1;
}
}
} else if (o->encoding == OBJ_ENCODING_HT) {
if (dictDelete((dict*)o->ptr, field) == C_OK) {
deleted = 1;
//进行收缩字典空间判断,进行字典空间收缩
if (htNeedsResize(o->ptr)) dictResize(o->ptr);
}
}
return deleted;
}
其他哈希对象操作的汇总
unsigned long hashTypeLength(const robj *o);//获取哈希对象中元素的个数
int hashTypeGetValue(robj *o, sds field, unsigned char **vstr, unsigned int *vlen, long long *vll);
//获取哈希对象中指定field的value
int hashTypeExists(robj *o, sds field) ;//判断哈希对象是否存在指定field
hashTypeIterator *hashTypeInitIterator(robj *subject);//初始化哈希对象的迭代器
void hashTypeReleaseIterator(hashTypeIterator *hi);//释放哈希对象的迭代器
int hashTypeNext(hashTypeIterator *hi);//将迭代器移到下一个节点
void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr,
unsigned int *vlen, long long *vll)//获取迭代器指到的键值对
二、哈希对象的redis命令
1、设置哈希对象的field-value的redis命令
//将哈希表key中的哈希对象的域field的值设置为value
void hsetCommand(client *c) {
int update;
robj *o;//查找key对应的对象
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
hashTypeTryConversion(o,c->argv,2,3);//尝试编码转换
update = hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY);//设置哈希对象的field的val
addReply(c, update ? shared.czero : shared.cone);//返回操作结果
signalModifiedKey(c->db,c->argv[1]);//触发被监视键已经被修改的信号
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty++;
}
void hsetnxCommand(client *c); /*将哈希表key中的哈希对象域field的值设置为value,当且仅当域field不存在。
相对hsetCommand,在设置哈希对象的field的val的时候,进行field是否存在的判断 */
void hmsetCommand(client *c);/*设置哈希表key中哈希对象多个域field的值,逻辑与hsetCommand,循环调用
hashTypeSet设置传入的多个field-value*/
2、获取哈希对象中的field-value的redis命令
//将哈希对象的指定field的value返回给client
static void addHashFieldToReply(client *c, robj *o, sds field);
//将哈希表中key对应的哈希对象的指定field的value返回给客户端
void hgetCommand(client *c) {
robj *o;//获取哈希表中指定key的哈希对象
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,OBJ_HASH)) return;
addHashFieldToReply(c, o, c->argv[2]->ptr);
}
void hmgetCommand(client *c);//将哈希表中key对应的哈希对象的指定多个field的value返回给客户端
//返回哈希表中key对应哈希对象中所有field、value或者field-value给客户端
void genericHgetallCommand(client *c, int flags) {
robj *o;
hashTypeIterator *hi;
int multiplier = 0;
int length, count = 0;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|| checkType(c,o,OBJ_HASH)) return;
if (flags & OBJ_HASH_KEY) multiplier++;
if (flags & OBJ_HASH_VALUE) multiplier++;
length = hashTypeLength(o) * multiplier;
addReplyMultiBulkLen(c, length);
hi = hashTypeInitIterator(o);
//遍历哈希对象
while (hashTypeNext(hi) != C_ERR) {
if (flags & OBJ_HASH_KEY) {//将迭代器指向的键值对的key返回给客户端
addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY);
count++;
}
if (flags & OBJ_HASH_VALUE) {//将迭代器指向的键值对的value返回给客户端
addHashIteratorCursorToReply(c, hi, OBJ_HASH_VALUE);
count++;
}
}
}
void hkeysCommand(client *c);//返回哈希表中key对应哈希对象中所有field给客户端
void hvalsCommand(client *c);//返回哈希表中key对应哈希对象中所有value给客户端
void hgetallCommand(client *c);//返回哈希表中key对应哈希对象中所有field-value给客户端
3、其余命令的汇总
void hincrbyCommand(client *c);//当value的值为整形时,添加val
void hincrbyfloatCommand(client *c);//当value的值为浮点数时,添加val
void hdelCommand(client *c);//调用hashTypeDelete删除哈希表中key对应的哈希对象的指定feild
void hlenCommand(client *c);//调用hashTypeLength获取哈希表中key对应的哈希对象的元素个数
void hstrlenCommand(client *c);//获取哈希表中key对应的哈希对象的指定field的value的字符串长度
void hexistsCommand(client *c);//返回给客户端指定key的指定field是否存在
void hscanCommand(client *c) ;//扫描操作