基础的hash相关的命令,t_hash.c

hset——hsetCommand

示例:HSET KEY_NAME FIELD VALUE

  • 如果字段是哈希表中的一个新建字段,并且值设置成功,返回 1 。 如果哈希表中域字段已经存在且旧值已被新值覆盖,返回 0 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
void hsetCommand(redisClient *c) {
int update;
robj *o;
// 取出或新创建哈希对象
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
// 如果需要的话,转换哈希对象的编码
hashTypeTryConversion(o,c->argv,2,3);
// 编码 field 和 value 对象以节约空间
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
// 设置 field 和 value 到 hash
update = hashTypeSet(o,c->argv[2],c->argv[3]);
// 返回状态:显示 field-value 对是新添加还是更新
addReply(c, update ? shared.czero : shared.cone);
// 发送键修改信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
// 将服务器设为脏
server.dirty++;
}

int hashTypeSet(robj *o, robj *field, robj *value) {
int update = 0;
// 添加到 ziplist
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *zl, *fptr, *vptr;
// 解码成字符串或者数字
field = getDecodedObject(field);
value = getDecodedObject(value);
// 遍历整个 ziplist ,尝试查找并更新 field (如果它已经存在的话)
zl = o->ptr;
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
if (fptr != NULL) {
// 定位到域 field
fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
if (fptr != NULL) {
/* Grab pointer to the value (fptr points to the field) */
// 定位到域的值
vptr = ziplistNext(zl, fptr);
redisAssert(vptr != NULL);
// 标识这次操作为更新操作
update = 1;
/* Delete value */
// 删除旧的键值对
zl = ziplistDelete(zl, &vptr);
/* Insert new value */
// 添加新的键值对
zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr));
}
}
// 如果这不是更新操作,那么这就是一个添加操作
if (!update) {
/* Push new field/value pair onto the tail of the ziplist */
// 将新的 field-value 对推入到 ziplist 的末尾
zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL);
zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL);
}

// 更新对象指针
o->ptr = zl;
// 释放临时对象
decrRefCount(field);
decrRefCount(value);
/* Check if the ziplist needs to be converted to a hash table */
// 检查在添加操作完成之后,是否需要将 ZIPLIST 编码转换成 HT 编码
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
hashTypeConvert(o, REDIS_ENCODING_HT);
// 添加到字典
} else if (o->encoding == REDIS_ENCODING_HT) {
// 添加或替换键值对到字典
// 添加返回 1 ,替换返回 0
if (dictReplace(o->ptr, field, value)) { /* Insert */
incrRefCount(field);
} else { /* Update */
update = 1;
}
incrRefCount(value);
} else {
redisPanic("Unknown hash encoding");
}
// 更新/添加指示变量
return update;
}

hsetnx——hsetnxCommand

示例:HSETNX KEY_NAME FIELD VALUE

  • 设置成功,返回 1 。 如果给定字段已经存在且没有操作被执行,返回 0 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void hsetnxCommand(redisClient *c) {
robj *o;
// 取出或新创建哈希对象
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
// 如果需要的话,转换哈希对象的编码
hashTypeTryConversion(o,c->argv,2,3);
// 如果 field-value 对已经存在
// 那么回复 0
if (hashTypeExists(o, c->argv[2])) {
addReply(c, shared.czero);
// 否则,设置 field-value 对
} else {
// 对 field 和 value 对象编码,以节省空间
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
// 设置
hashTypeSet(o,c->argv[2],c->argv[3]);
// 回复 1 ,表示设置成功
addReply(c, shared.cone);
// 发送键修改信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
// 将数据库设为脏
server.dirty++;
}
}

hget——hgetCommand

示例:HGET KEY_NAME FIELD_NAME

  • 返回给定字段的值。如果给定的字段或 key 不存在时,返回 nil 。
1
2
3
4
5
6
7
void hgetCommand(redisClient *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
// 取出并返回域的值
addHashFieldToReply(c, o, c->argv[2]);
}

hmset——hmsetCommand

示例:HMSET KEY_NAME FIELD1 VALUE1 …FIELDN VALUEN

  • 如果命令执行成功,返回 OK 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void hmsetCommand(redisClient *c) {
int i;
robj *o;
// field-value 参数必须成对出现
if ((c->argc % 2) == 1) {
addReplyError(c,"wrong number of arguments for HMSET");
return;
}
// 取出或新创建哈希对象
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
// 如果需要的话,转换哈希对象的编码
hashTypeTryConversion(o,c->argv,2,c->argc-1);
// 遍历并设置所有 field-value 对
for (i = 2; i < c->argc; i += 2) {
// 编码 field-value 对,以节约空间
hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]);
// 设置
hashTypeSet(o,c->argv[i],c->argv[i+1]);
}
// 向客户端发送回复
addReply(c, shared.ok);
// 发送键修改信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
// 将数据库设为脏
server.dirty++;
}

hmget——hmgetCommand

示例:HMGET KEY_NAME FIELD1…FIELDN

  • 一个包含多个给定字段关联值的表,表值的排列顺序和指定字段的请求顺序一样。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void hmgetCommand(redisClient *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. */
// 取出哈希对象
o = lookupKeyRead(c->db, c->argv[1]);
// 对象存在,检查类型
if (o != NULL && o->type != REDIS_HASH) {
addReply(c, shared.wrongtypeerr);
return;
}
// 获取多个 field 的值
addReplyMultiBulkLen(c, c->argc-2);
for (i = 2; i < c->argc; i++) {
addHashFieldToReply(c, o, c->argv[i]);
}
}

hincrby——hincrbyCommand

示例:HINCRBY KEY_NAME FIELD_NAME INCR_BY_NUMBER

  • 执行 HINCRBY 命令之后,哈希表中字段的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void hincrbyCommand(redisClient *c) {
long long value, incr, oldvalue;
robj *o, *current, *new;
// 取出 incr 参数的值,并创建对象
if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
// 取出或新创建哈希对象
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
// 取出 field 的当前值
if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
// 取出值的整数表示
if (getLongLongFromObjectOrReply(c,current,&value,
"hash value is not an integer") != REDIS_OK) {
decrRefCount(current);
return;
}
decrRefCount(current);
} else {
// 如果值当前不存在,那么默认为 0
value = 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 = createStringObjectFromLongLong(value);
// 编码值对象
hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
// 关联键和新的值对象,如果已经有对象存在,那么用新对象替换它
hashTypeSet(o,c->argv[2],new);
decrRefCount(new);
// 将计算结果用作回复
addReplyLongLong(c,value);
// 发送键修改信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
// 将数据库设为脏
server.dirty++;
}

hincrbyfloat——hincrbyfloatCommand

示例:HINCRBYFLOAT KEY_NAME FIELD_NAME INCR_BY_NUMBER

  • 执行 Hincrbyfloat 命令之后,哈希表中字段的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void hincrbyfloatCommand(redisClient *c) {
double long value, incr;
robj *o, *current, *new, *aux;
// 取出 incr 参数
if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
// 取出或新创建哈希对象
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
// 取出值对象
if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
// 从值对象中取出浮点值
if (getLongDoubleFromObjectOrReply(c,current,&value,
"hash value is not a valid float") != REDIS_OK) {
decrRefCount(current);
return;
}
decrRefCount(current);
} else {
// 值对象不存在,默认值为 0
value = 0;
}
// 计算结果
value += incr;
// 为计算结果创建值对象
new = createStringObjectFromLongDouble(value);
// 编码值对象
hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
// 关联键和新的值对象,如果已经有对象存在,那么用新对象替换它
hashTypeSet(o,c->argv[2],new);
// 返回新的值对象作为回复
addReplyBulk(c,new);
// 发送键修改信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_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. */
// 在传播 INCRBYFLOAT 命令时,总是用 SET 命令来替换 INCRBYFLOAT 命令
// 从而防止因为不同的浮点精度和格式化造成 AOF 重启时的数据不一致
aux = createStringObject("HSET",4);
rewriteClientCommandArgument(c,0,aux);
decrRefCount(aux);
rewriteClientCommandArgument(c,3,new);
decrRefCount(new);
}

hdel——hdelCommand

示例:HDEL KEY_NAME FIELD1.. FIELDN

  • 被成功删除字段的数量,不包括被忽略的字段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void hdelCommand(redisClient *c) {
robj *o;
int j, deleted = 0, keyremoved = 0;
// 取出对象
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
// 删除指定域值对
for (j = 2; j < c->argc; j++) {
if (hashTypeDelete(o,c->argv[j])) {
// 成功删除一个域值对时进行计数
deleted++;
// 如果哈希已经为空,那么删除这个对象
if (hashTypeLength(o) == 0) {
dbDelete(c->db,c->argv[1]);
keyremoved = 1;
break;
}
}
}
// 只要有至少一个域值对被修改了,那么执行以下代码
if (deleted) {
// 发送键修改信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
// 发送事件通知
if (keyremoved)
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],
c->db->id);
// 将数据库设为脏
server.dirty += deleted;
}
// 将成功删除的域值对数量作为结果返回给客户端
addReplyLongLong(c,deleted);
}

hlen——hlenCommand

示例:HLEN KEY_NAME

  • 哈希表中字段的数量。 当 key 不存在时,返回 0 。
1
2
3
4
5
6
7
8
void hlenCommand(redisClient *c) {
robj *o;
// 取出哈希对象
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
// 回复
addReplyLongLong(c,hashTypeLength(o));
}

hkeys——hkeysCommand

示例:HKEYS key

  • 包含哈希表中所有域(field)列表。 当 key 不存在时,返回一个空列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void hkeysCommand(redisClient *c) {
genericHgetallCommand(c,REDIS_HASH_KEY);
}
void genericHgetallCommand(redisClient *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,REDIS_HASH)) return;
// 计算要取出的元素数量
if (flags & REDIS_HASH_KEY) multiplier++;
if (flags & REDIS_HASH_VALUE) multiplier++;
length = hashTypeLength(o) * multiplier;
addReplyMultiBulkLen(c, length);
// 迭代节点,并取出元素
hi = hashTypeInitIterator(o);
while (hashTypeNext(hi) != REDIS_ERR) {
// 取出键
if (flags & REDIS_HASH_KEY) {
addHashIteratorCursorToReply(c, hi, REDIS_HASH_KEY);
count++;
}
// 取出值
if (flags & REDIS_HASH_VALUE) {
addHashIteratorCursorToReply(c, hi, REDIS_HASH_VALUE);
count++;
}
}
// 释放迭代器
hashTypeReleaseIterator(hi);
redisAssert(count == length);
}

hvals——hvalsCommand

示例:HVALS KEY_NAME

  • 一个包含哈希表中所有域(field)值的列表。 当 key 不存在时,返回一个空表。
1
2
3
void hvalsCommand(redisClient *c) {
genericHgetallCommand(c,REDIS_HASH_VALUE);
}

hgetall——hgetallCommand

示例:HGETALL KEY_NAME

  • 以列表形式返回哈希表的字段及字段值。 若 key 不存在,返回空列表。
1
2
3
void hgetallCommand(redisClient *c) {
genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE);
}

hexists——hexistsCommand

示例:HEXISTS KEY_NAME FIELD_NAME

  • 如果哈希表含有给定字段,返回 1 。 如果哈希表不含有给定字段,或 key 不存在,返回 0 。
1
2
3
4
5
6
7
8
void hexistsCommand(redisClient *c) {
robj *o;
// 取出哈希对象
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
// 检查给定域是否存在
addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero);
}

hscan——hscanCommand

示例:HSCAN key cursor [MATCH pattern] [COUNT count]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
void hscanCommand(redisClient *c) {
robj *o;
unsigned long cursor;
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == REDIS_ERR) return;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
scanGenericCommand(c,o,cursor);
}

//db.c
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
int rv;
int i, j;
char buf[REDIS_LONGSTR_SIZE];
list *keys = listCreate();
listNode *node, *nextnode;
long count = 10;
sds pat;
int patlen, use_pattern = 0;
dict *ht;
/* Object must be NULL (to iterate keys names), or the type of the object
* must be Set, Sorted Set, or Hash. */
// 输入类型检查
redisAssert(o == NULL || o->type == REDIS_SET || o->type == REDIS_HASH ||
o->type == REDIS_ZSET);
/* Set i to the first option argument. The previous one is the cursor. */
// 设置第一个选项参数的索引位置
// 0 1 2 3
// SCAN OPTION <op_arg> SCAN 命令的选项值从索引 2 开始
// HSCAN <key> OPTION <op_arg> 而其他 *SCAN 命令的选项值从索引 3 开始
i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */
/* Step 1: Parse options. */
// 分析选项参数
while (i < c->argc) {
j = c->argc - i;
// COUNT <number>
if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) {
if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL)
!= REDIS_OK)
{
goto cleanup;
}
if (count < 1) {
addReply(c,shared.syntaxerr);
goto cleanup;
}
i += 2;
// MATCH <pattern>
} else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) {
pat = c->argv[i+1]->ptr;
patlen = sdslen(pat);
/* The pattern always matches if it is exactly "*", so it is
* equivalent to disabling it. */
use_pattern = !(pat[0] == '*' && patlen == 1);
i += 2;
// error
} else {
addReply(c,shared.syntaxerr);
goto cleanup;
}
}
/* Step 2: Iterate the collection.
*
* Note that if the object is encoded with a ziplist, intset, or any other
* representation that is not a hash table, we are sure that it is also
* composed of a small number of elements. So to avoid taking state we
* just return everything inside the object in a single call, setting the
* cursor to zero to signal the end of the iteration. */
// 如果对象的底层实现为 ziplist 、intset 而不是哈希表,
// 那么这些对象应该只包含了少量元素,
// 为了保持不让服务器记录迭代状态的设计
// 我们将 ziplist 或者 intset 里面的所有元素都一次返回给调用者
// 并向调用者返回游标(cursor) 0
/* Handle the case of a hash table. */
ht = NULL;
if (o == NULL) {
// 迭代目标为数据库
ht = c->db->dict;
} else if (o->type == REDIS_SET && o->encoding == REDIS_ENCODING_HT) {
// 迭代目标为 HT 编码的集合
ht = o->ptr;
} else if (o->type == REDIS_HASH && o->encoding == REDIS_ENCODING_HT) {
// 迭代目标为 HT 编码的哈希
ht = o->ptr;
count *= 2; /* We return key / value for this type. */
} else if (o->type == REDIS_ZSET && o->encoding == REDIS_ENCODING_SKIPLIST) {
// 迭代目标为 HT 编码的跳跃表
zset *zs = o->ptr;
ht = zs->dict;
count *= 2; /* We return key / value for this type. */
}
if (ht) {
void *privdata[2];
/* We pass two pointers to the callback: the list to which it will
* add new elements, and the object containing the dictionary so that
* it is possible to fetch more data in a type-dependent way. */
// 我们向回调函数传入两个指针:
// 一个是用于记录被迭代元素的列表
// 另一个是字典对象
// 从而实现类型无关的数据提取操作
privdata[0] = keys;
privdata[1] = o;
do {
cursor = dictScan(ht, cursor, scanCallback, privdata);
} while (cursor && listLength(keys) < count);
} else if (o->type == REDIS_SET) {
int pos = 0;
int64_t ll;
while(intsetGet(o->ptr,pos++,&ll))
listAddNodeTail(keys,createStringObjectFromLongLong(ll));
cursor = 0;
} else if (o->type == REDIS_HASH || o->type == REDIS_ZSET) {
unsigned char *p = ziplistIndex(o->ptr,0);
unsigned char *vstr;
unsigned int vlen;
long long vll;
while(p) {
ziplistGet(p,&vstr,&vlen,&vll);
listAddNodeTail(keys,
(vstr != NULL) ? createStringObject((char*)vstr,vlen) :
createStringObjectFromLongLong(vll));
p = ziplistNext(o->ptr,p);
}
cursor = 0;
} else {
redisPanic("Not handled encoding in SCAN.");
}
/* Step 3: Filter elements. */
node = listFirst(keys);
while (node) {
robj *kobj = listNodeValue(node);
nextnode = listNextNode(node);
int filter = 0;
/* Filter element if it does not match the pattern. */
if (!filter && use_pattern) {
if (sdsEncodedObject(kobj)) {
if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
filter = 1;
} else {
char buf[REDIS_LONGSTR_SIZE];
int len;
redisAssert(kobj->encoding == REDIS_ENCODING_INT);
len = ll2string(buf,sizeof(buf),(long)kobj->ptr);
if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;
}
}
/* Filter element if it is an expired key. */
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
/* Remove the element and its associted value if needed. */
if (filter) {
decrRefCount(kobj);
listDelNode(keys, node);
}
/* If this is a hash or a sorted set, we have a flat list of
* key-value elements, so if this element was filtered, remove the
* value, or skip it if it was not filtered: we only match keys. */
if (o && (o->type == REDIS_ZSET || o->type == REDIS_HASH)) {
node = nextnode;
nextnode = listNextNode(node);
if (filter) {
kobj = listNodeValue(node);
decrRefCount(kobj);
listDelNode(keys, node);
}
}
node = nextnode;
}
/* Step 4: Reply to the client. */
addReplyMultiBulkLen(c, 2);
rv = snprintf(buf, sizeof(buf), "%lu", cursor);
redisAssert(rv < sizeof(buf));
addReplyBulkCBuffer(c, buf, rv);
addReplyMultiBulkLen(c, listLength(keys));
while ((node = listFirst(keys)) != NULL) {
robj *kobj = listNodeValue(node);
addReplyBulk(c, kobj);
decrRefCount(kobj);
listDelNode(keys, node);
}
cleanup:
listSetFreeMethod(keys,decrRefCountVoid);
listRelease(keys);
}
命令 作用 ziplist编码时间复杂度 hashtable编码时间复杂度
HSET 设置指定的哈希表中 key 的值 平均O(n), 最坏O(N2) O(1)
HGET 获取指定的哈希表中 key 的值 O(N2) O(1)
HEXISTS 判断哈希表中的 field 的值是否存在 O(N2) O(1)
HDEL 删除指定哈希表中的field,可以为多个 O(N2) O(1)
HLEN 返回指定哈希表中的长度 O(1) O(1)
HGETALL 返回指定哈希表中的所有元素 O(n) O(n)