2.源码解析

1.相关命令如下:

    {"hset",hsetCommand,,"wmF",,NULL,,,,,},
{"hsetnx",hsetnxCommand,,"wmF",,NULL,,,,,},
{"hget",hgetCommand,,"rF",,NULL,,,,,},
{"hmset",hmsetCommand,-,"wm",,NULL,,,,,},
{"hmget",hmgetCommand,-,"r",,NULL,,,,,},
{"hincrby",hincrbyCommand,,"wmF",,NULL,,,,,},
{"hincrbyfloat",hincrbyfloatCommand,,"wmF",,NULL,,,,,},
{"hdel",hdelCommand,-,"wF",,NULL,,,,,},
{"hlen",hlenCommand,,"rF",,NULL,,,,,},
{"hstrlen",hstrlenCommand,,"rF",,NULL,,,,,},
{"hkeys",hkeysCommand,,"rS",,NULL,,,,,},
{"hvals",hvalsCommand,,"rS",,NULL,,,,,},
{"hgetall",hgetallCommand,,"r",,NULL,,,,,},
{"hexists",hexistsCommand,,"rF",,NULL,,,,,},
{"hscan",hscanCommand,-,"rR",,NULL,,,,,},

2.ziplist数据结构

/* We use this function to receive information about a ziplist entry.
* Note that this is not how the data is actually encoded, is just what we
* get filled by a function in order to operate more easily. */
typedef struct zlentry {
unsigned int prevrawlensize; /* Bytes used to encode the previous entry len*/
unsigned int prevrawlen; /* Previous entry len. */
unsigned int lensize; /* Bytes used to encode this entry type/len.
For example strings have a 1, 2 or 5 bytes
header. Integers always use a single byte.*/
unsigned int len; /* Bytes used to represent the actual entry.
For strings this is just the string length
while for integers it is 1, 2, 3, 4, 8 or
0 (for 4 bit immediate) depending on the
number range. */
unsigned int headersize; /* prevrawlensize + lensize. */
unsigned char encoding; /* Set to ZIP_STR_* or ZIP_INT_* depending on
the entry encoding. However for 4 bits
immediate integers this can assume a range
of values and must be range-checked. */
unsigned char *p; /* Pointer to the very start of the entry, that
is, this points to prev-entry-len field. */
} zlentry;

3.hashtable数据结构

typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry; typedef struct dictType {
uint64_t (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType; /* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht; typedef struct dict {
dictType *type;
void *privdata;
dictht ht[];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;

hset

// t_hash.c, set key field value
void hsetCommand(client *c) {
int update;
robj *o;
// 1. 查找hash的key是否存在,不存在则新建一个,然后在其上进行数据操作
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[])) == NULL) return;
// 2. 检查2-3个参数是否需要将简单版(ziplist)hash表转换为复杂的hash表,转换后的表通过 o->ptr 体现
hashTypeTryConversion(o,c->argv,,);
// 3. 添加kv到 o 的hash表中
update = hashTypeSet(o,c->argv[]->ptr,c->argv[]->ptr,HASH_SET_COPY);
addReply(c, update ? shared.czero : shared.cone);
// 变更命令传播
signalModifiedKey(c->db,c->argv[]);
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[],c->db->id);
server.dirty++;
} // 1. 获取db外部的key, 即整体hash数据实例
// t_hash.c
robj *hashTypeLookupWriteOrCreate(client *c, robj *key) {
robj *o = lookupKeyWrite(c->db,key);
if (o == NULL) {
// 此处创建的hashObject是以 ziplist 形式的
o = createHashObject();
dbAdd(c->db,key,o);
} else {
// 不是hash类型的键已存在,不可覆盖,返回错误
if (o->type != OBJ_HASH) {
addReply(c,shared.wrongtypeerr);
return NULL;
}
}
return o;
}
// object.c, 创建hashObject, 以 ziplist 形式创建
robj *createHashObject(void) {
unsigned char *zl = ziplistNew();
robj *o = createObject(OBJ_HASH, zl);
o->encoding = OBJ_ENCODING_ZIPLIST;
return o;
}
// ziplist.c
static unsigned char *createList() {
unsigned char *zl = ziplistNew();
zl = ziplistPush(zl, (unsigned char*)"foo", , ZIPLIST_TAIL);
zl = ziplistPush(zl, (unsigned char*)"quux", , ZIPLIST_TAIL);
zl = ziplistPush(zl, (unsigned char*)"hello", , ZIPLIST_HEAD);
zl = ziplistPush(zl, (unsigned char*)"", , ZIPLIST_TAIL);
return zl;
} // 2. 检查参数,是否需要将 ziplist 形式的hash表转换为真正的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. */
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
int i; if (o->encoding != OBJ_ENCODING_ZIPLIST) return; for (i = start; i <= end; i++) {
// 参数大于设置的 hash_max_ziplist_value (默认: 64)时,会直接将 ziplist 转换为 ht
// OBJ_ENCODING_RAW, OBJ_ENCODING_EMBSTR
// 循环检查参数,只要发生了一次转换就结束检查(没必要继续了)
if (sdsEncodedObject(argv[i]) &&
sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
{
// 这个转换过程很有意思,我们深入看看
hashTypeConvert(o, OBJ_ENCODING_HT);
break;
}
}
}
// t_hash.c, 转换编码方式 (如上, ziplist -> ht)
void hashTypeConvert(robj *o, int enc) {
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
// 此处我们只处理这种情况
hashTypeConvertZiplist(o, enc);
} else if (o->encoding == OBJ_ENCODING_HT) {
serverPanic("Not implemented");
} else {
serverPanic("Unknown hash encoding");
}
}
// t_hash.c, 转换编码 ziplist 为目标 enc (实际只能是 OBJ_ENCODING_HT)
void hashTypeConvertZiplist(robj *o, int enc) {
serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST); if (enc == OBJ_ENCODING_ZIPLIST) {
/* Nothing to do... */ } else if (enc == OBJ_ENCODING_HT) {
hashTypeIterator *hi;
dict *dict;
int ret;
// 迭代器创建
hi = hashTypeInitIterator(o);
// 一个hash的数据结构就是一个 dict, 从这个级别来说, hash 与 db 是一个级别的
dict = dictCreate(&hashDictType, NULL);
// 依次迭代 o, 赋值到 hi->fptr, hi->vptr
// 依次添加到 dict 中
while (hashTypeNext(hi) != C_ERR) {
sds key, value;
// 从 hi->fptr 中获取key
// 从 hi->vptr 中获取value
key = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
// 添加到 dict 中
ret = dictAdd(dict, key, value);
if (ret != DICT_OK) {
serverLogHexDump(LL_WARNING,"ziplist with dup elements dump",
o->ptr,ziplistBlobLen(o->ptr));
serverPanic("Ziplist corruption detected");
}
}
// 释放迭代器
hashTypeReleaseIterator(hi);
zfree(o->ptr);
// 将变更反映到o对象上返回
o->encoding = OBJ_ENCODING_HT;
o->ptr = dict;
} else {
serverPanic("Unknown hash encoding");
}
}
// 2.1. 迭代ziplist元素
// t_hash.c, 迭代器
/* 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. */
int hashTypeNext(hashTypeIterator *hi) {
if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
unsigned char *zl;
unsigned char *fptr, *vptr;
// 每次都是基于原始字符器进行计算偏移
// 迭代的是 fptr,vptr
zl = hi->subject->ptr;
fptr = hi->fptr;
vptr = hi->vptr;
// 第一次查找时使用index查找,后续则使用 fptr,vptr 进行迭代
if (fptr == NULL) {
/* Initialize cursor */
serverAssert(vptr == NULL);
fptr = ziplistIndex(zl, );
} 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 */
hi->fptr = fptr;
hi->vptr = vptr;
} else if (hi->encoding == OBJ_ENCODING_HT) {
if ((hi->de = dictNext(hi->di)) == NULL) return C_ERR;
} else {
serverPanic("Unknown hash encoding");
}
return C_OK;
}
// ziplist.c, 查找 index 的元素
/* Returns an offset to use for iterating with ziplistNext. When the given
* index is negative, the list is traversed back to front. When the list
* doesn't contain an element at the provided index, NULL is returned. */
unsigned char *ziplistIndex(unsigned char *zl, int index) {
unsigned char *p;
unsigned int prevlensize, prevlen = ;
if (index < ) {
// 小于0时,反向查找
index = (-index)-;
p = ZIPLIST_ENTRY_TAIL(zl);
if (p[] != ZIP_END) {
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
while (prevlen > && index--) {
p -= prevlen;
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
}
}
} else {
p = ZIPLIST_ENTRY_HEAD(zl);
while (p[] != ZIP_END && index--) {
p += zipRawEntryLength(p);
}
}
// 迭代完成还没找到元素 p[0]=ZIP_END
// index 超出整体ziplist大小则遍历完成后 index>0
return (p[] == ZIP_END || index > ) ? NULL : p;
}
// ziplist.c, 由 fptr,vptr 进行迭代元素
/* Return pointer to next entry in ziplist.
*
* zl is the pointer to the ziplist
* p is the pointer to the current element
*
* The element after 'p' is returned, otherwise NULL if we are at the end. */
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p) {
((void) zl); /* "p" could be equal to ZIP_END, caused by ziplistDelete,
* and we should return NULL. Otherwise, we should return NULL
* when the *next* element is ZIP_END (there is no next entry). */
if (p[] == ZIP_END) {
return NULL;
}
// 当前指针偏移当前元素长度(根据ziplist协议),即到下一元素指针位置
p += zipRawEntryLength(p);
if (p[] == ZIP_END) {
return NULL;
} return p;
}
/* Return the total number of bytes used by the entry pointed to by 'p'. */
static unsigned int zipRawEntryLength(unsigned char *p) {
unsigned int prevlensize, encoding, lensize, len;
ZIP_DECODE_PREVLENSIZE(p, prevlensize);
ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
return prevlensize + lensize + len;
} // 2.2. t_hash.c, 获取 hashTypeIterator 的具体值,写入 vstr, vlen 中
/* Return the key or value at the current iterator position as a new
* SDS string. */
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);
}
/* Higher level function of hashTypeCurrent*() that returns the hash value
* at current iterator position.
*
* 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.
*
* 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. */
void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) {
if (hi->encoding == OBJ_ENCODING_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");
}
} // t_hash.c, 从ziplist中获取某个 hashTypeIterator 的具体值,结果定稿 vstr, vlen
/* Get the field or value at iterator cursor, for an iterator on a hash value
* encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */
void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what,
unsigned char **vstr,
unsigned int *vlen,
long long *vll)
{
int ret; serverAssert(hi->encoding == OBJ_ENCODING_ZIPLIST);
// OBJ_HASH_KEY 从 fptr 中获取, 否则从 vptr 中获取
if (what & OBJ_HASH_KEY) {
ret = ziplistGet(hi->fptr, vstr, vlen, vll);
serverAssert(ret);
} else {
ret = ziplistGet(hi->vptr, vstr, vlen, vll);
serverAssert(ret);
}
}
// ziplist.c,
/* Get entry pointed to by 'p' and store in either '*sstr' or 'sval' depending
* on the encoding of the entry. '*sstr' is always set to NULL to be able
* to find out whether the string pointer or the integer value was set.
* Return 0 if 'p' points to the end of the ziplist, 1 otherwise. */
unsigned int ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *slen, long long *sval) {
zlentry entry;
if (p == NULL || p[] == ZIP_END) return ;
if (sstr) *sstr = NULL;
// 按照ziplist的编码协议, 获取头部信息
zipEntry(p, &entry);
if (ZIP_IS_STR(entry.encoding)) {
if (sstr) {
*slen = entry.len;
*sstr = p+entry.headersize;
}
} else {
if (sval) {
*sval = zipLoadInteger(p+entry.headersize,entry.encoding);
}
}
return ;
}
// ziplist.c, 解析原始字符串为 zlentry
/* Return a struct with all information about an entry. */
static void zipEntry(unsigned char *p, zlentry *e) {
// 按照ziplist的编码协议,依次读取 prevrawlensize, prevrawlen
ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
// 指向下一位置偏移,按照ziplist的编码协议,依次读取 encoding, lensize, len
ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
// 除去header得到 body偏移
e->headersize = e->prevrawlensize + e->lensize;
e->p = p;
}

header

// ziplist.c
/* Decode the length of the previous element, from the perspective of the entry
* pointed to by 'ptr'. */
#define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do { \
// 解析第1个字符为 prevlensize
ZIP_DECODE_PREVLENSIZE(ptr, prevlensize); \
if ((prevlensize) == ) { \
(prevlen) = (ptr)[]; \
} else if ((prevlensize) == ) { \
assert(sizeof((prevlensize)) == ); \
// 当ptr[0]>254时,代表内容有点大,需要使用 5个字符保存上一字符长度
memcpy(&(prevlen), ((char*)(ptr)) + , ); \
memrev32ifbe(&prevlen); \
} \
} while();
/* Decode the number of bytes required to store the length of the previous
* element, from the perspective of the entry pointed to by 'ptr'. */
#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do { \
if ((ptr)[] < ZIP_BIGLEN) { \
(prevlensize) = ; \
} else { \
(prevlensize) = ; \
} \
} while();
/* Decode the length encoded in 'ptr'. The 'encoding' variable will hold the
* entries encoding, the 'lensize' variable will hold the number of bytes
* required to encode the entries length, and the 'len' variable will hold the
* entries length. */
#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do { \
// 解析第1个字符为 编码格式 &ZIP_STR_MASK=0xc0
ZIP_ENTRY_ENCODING((ptr), (encoding)); \
if ((encoding) < ZIP_STR_MASK) { \
// 0 << 6 =0
// 具体解析如下代码,
if ((encoding) == ZIP_STR_06B) { \
(lensize) = ; \
(len) = (ptr)[] & 0x3f; \
}
// 1 << 6 =64
else if ((encoding) == ZIP_STR_14B) { \
(lensize) = ; \
(len) = (((ptr)[] & 0x3f) << ) | (ptr)[]; \
}
// 2 << 6 =128
else if (encoding == ZIP_STR_32B) { \
(lensize) = ; \
(len) = ((ptr)[] << ) | \
((ptr)[] << ) | \
((ptr)[] << ) | \
((ptr)[]); \
} else { \
assert(NULL); \
} \
} else { \
// 超过 0xc0 的长度了,直接使用 1,2,3,4 表示len
(lensize) = ; \
(len) = zipIntSize(encoding); \
} \
} while();
/* Extract the encoding from the byte pointed by 'ptr' and set it into
* 'encoding'. */
#define ZIP_ENTRY_ENCODING(ptr, encoding) do { \
(encoding) = (ptr[]); \
if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \
} while() /* Different encoding/length possibilities */
#define ZIP_STR_MASK 0xc0
#define ZIP_INT_MASK 0x30
#define ZIP_STR_06B (0 << 6) // 0x00
#define ZIP_STR_14B (1 << 6) // 0x40
#define ZIP_STR_32B (2 << 6) // 0x80
#define ZIP_INT_16B (0xc0 | 0<<4) // 0xc0
#define ZIP_INT_32B (0xc0 | 1<<4) // 0xd0
#define ZIP_INT_64B (0xc0 | 2<<4) // 0xe0
#define ZIP_INT_24B (0xc0 | 3<<4) // 0xf0
#define ZIP_INT_8B 0xfe // 0xfe

添加kv到对应的key实例中:

// 3. 添加kv到 hash表中, 稍微复杂
// t_hash.c, 做变更到hash表中
int hashTypeSet(robj *o, sds field, sds value, int flags) {
int update = ;
// 针对ziplist 的添加, 与 ht 编码的添加, 自然是分别处理
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
unsigned char *zl, *fptr, *vptr; zl = o->ptr;
// 找到ziplist 的头节点指针
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
if (fptr != NULL) {
// 尝试查找该 field 对应的元素(从1开始),如果找到则先删除原值,然后统一添加
fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), );
if (fptr != NULL) {
/* Grab pointer to the value (fptr points to the field) */
// value 不可以为null, 否则 ziplist 将无法工作
vptr = ziplistNext(zl, fptr);
serverAssert(vptr != NULL);
update = ; /* Delete value */
// 先删除旧的 value, 再以插入的形式更新, 后续讲删除时再详解
zl = ziplistDelete(zl, &vptr); /* Insert new value */
// 重点,将value添加到 ziplist 中
zl = ziplistInsert(zl, vptr, (unsigned char*)value,
sdslen(value));
}
}
// 没有找到对应元素,则直接将元素添加到尾部即可
if (!update) {
/* Push new field/value pair onto the tail of the ziplist */
zl = ziplistPush(zl, (unsigned char*)field, sdslen(field),
ZIPLIST_TAIL);
zl = ziplistPush(zl, (unsigned char*)value, sdslen(value),
ZIPLIST_TAIL);
}
o->ptr = zl; /* Check if the ziplist needs to be converted to a hash table */
// 大于设置的阀值后,转换ziplist为ht(默认: 512)
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) {
sdsfree(dictGetVal(de));
if (flags & HASH_SET_TAKE_VALUE) {
dictGetVal(de) = value;
value = NULL;
} else {
dictGetVal(de) = sdsdup(value);
}
update = ;
} 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. */
if (flags & HASH_SET_TAKE_FIELD && field) sdsfree(field);
if (flags & HASH_SET_TAKE_VALUE && value) sdsfree(value);
return update;
}
// 3.1. 使用ziplist进行保存 field -> value
// ziplist.c, 查找某个 field 是否存在于ziplist中
/* Find pointer to the entry equal to the specified entry. Skip 'skip' entries
* between every comparison. Returns NULL when the field could not be found. */
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) {
int skipcnt = ;
unsigned char vencoding = ;
long long vll = ; while (p[] != ZIP_END) {
unsigned int prevlensize, encoding, lensize, len;
unsigned char *q;
// 解析整个字符串p的 prevlensize,encoding,lensize,len
ZIP_DECODE_PREVLENSIZE(p, prevlensize);
ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
q = p + prevlensize + lensize;
// 传入1, 代表要跳过一个元素, 比如: 查找key时,跳过1个v,然后继续迭代
// 跳过了n个元素后,再从此开始key的比对过程
if (skipcnt == ) {
/* Compare current entry with specified entry */
// 针对不同的编码使用不同的比较方式
if (ZIP_IS_STR(encoding)) {
// 找到相应的元素,直接返回 p 指针
if (len == vlen && memcmp(q, vstr, vlen) == ) {
return p;
}
} else {
/* Find out if the searched field can be encoded. Note that
* we do it only the first time, once done vencoding is set
* to non-zero and vll is set to the integer value. */
if (vencoding == ) {
if (!zipTryEncoding(vstr, vlen, &vll, &vencoding)) {
/* If the entry can't be encoded we set it to
* UCHAR_MAX so that we don't retry again the next
* time. */
vencoding = UCHAR_MAX;
}
/* Must be non-zero by now */
assert(vencoding);
} /* Compare current entry with specified entry, do it only
* if vencoding != UCHAR_MAX because if there is no encoding
* possible for the field it can't be a valid integer. */
if (vencoding != UCHAR_MAX) {
long long ll = zipLoadInteger(q, encoding);
if (ll == vll) {
return p;
}
}
} /* Reset skip count */
// 查找一次,跳过skip次
skipcnt = skip;
} else {
/* Skip entry */
skipcnt--;
} /* Move to next entry */
p = q + len;
} return NULL;
}
// ziplist.c, 添加value到ziplist中
// zl:ziplist实例, p:要插入的key字串, s:要插入的value字串, len:要插入的value的长度
/* Insert an entry at "p". */
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
return __ziplistInsert(zl,p,s,slen);
}
/* Insert item at "p". */
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen;
unsigned int prevlensize, prevlen = ;
size_t offset;
int nextdiff = ;
unsigned char encoding = ;
long long value = ; /* initialized to avoid warning. Using a value
that is easy to see if for some reason
we use it uninitialized. */
zlentry tail; /* Find out prevlen for the entry that is inserted. */
if (p[] != ZIP_END) {
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
} else {
unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
if (ptail[] != ZIP_END) {
prevlen = zipRawEntryLength(ptail);
}
} /* See if the entry can be encoded */
if (zipTryEncoding(s,slen,&value,&encoding)) {
/* 'encoding' is set to the appropriate integer encoding */
reqlen = zipIntSize(encoding);
} else {
/* 'encoding' is untouched, however zipEncodeLength will use the
* string length to figure out how to encode it. */
reqlen = slen;
}
/* We need space for both the length of the previous entry and
* the length of the payload. */
// 加上prevlen,encoding,slen 的长度,以计算value的存放位置
reqlen += zipPrevEncodeLength(NULL,prevlen);
reqlen += zipEncodeLength(NULL,encoding,slen); /* When the insert position is not equal to the tail, we need to
* make sure that the next entry can hold this entry's length in
* its prevlen field. */
nextdiff = (p[] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : ; /* Store offset because a realloc may change the address of zl. */
// 存储当前偏移位置,以便在扩容之后,还能找到相应位置
// p = p -zl + zl
offset = p-zl;
zl = ziplistResize(zl,curlen+reqlen+nextdiff);
p = zl+offset; /* Apply memory move when necessary and update tail offset. */
if (p[] != ZIP_END) {
/* Subtract one because of the ZIP_END bytes */
// 字符拷贝
memmove(p+reqlen,p-nextdiff,curlen-offset-+nextdiff); /* Encode this entry's raw length in the next entry. */
zipPrevEncodeLength(p+reqlen,reqlen); /* Update offset for tail */
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen); /* When the tail contains more than one entry, we need to take
* "nextdiff" in account as well. Otherwise, a change in the
* size of prevlen doesn't have an effect on the *tail* offset. */
zipEntry(p+reqlen, &tail);
if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
}
} else {
/* This element will be the new tail. */
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
} /* When nextdiff != 0, the raw length of the next entry has changed, so
* we need to cascade the update throughout the ziplist */
if (nextdiff != ) {
// 如果本次更新后数据位置变化,则需要更新后续的元素位置
offset = p-zl;
zl = __ziplistCascadeUpdate(zl,p+reqlen);
p = zl+offset;
} /* Write the entry */
// 将 value 写入 p 中, 即写入了 ziplist 中
p += zipPrevEncodeLength(p,prevlen);
p += zipEncodeLength(p,encoding,slen);
if (ZIP_IS_STR(encoding)) {
memcpy(p,s,slen);
} else {
zipSaveInteger(p,value,encoding);
}
ZIPLIST_INCR_LENGTH(zl,);
return zl;
}
// 另外,如果没有旧的元素值时,直接在hash表的末尾添加对应的field->value 即可
// ziplist.c, 在尾部进行添加元素,没有许多的情况要考虑,但是代码完全复用 __ziplistInsert()
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {
unsigned char *p;
p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
return __ziplistInsert(zl,p,s,slen);
}

深入理解ziplist

 看起来没ziplist好像没那么简单呢,为啥还要搞这么复杂呢?其实以上代码,仅是在人看来复杂,对机器来说就是更多的移位计算操作,多消耗点cpu就换来了空间上的节省,是可以的。软件本身的复杂性带来了效益,是软件的价值体现,所以,并非所有的东西都是简单即美。

  接下来,我们来看一下使用 HT 的编码又如何存储field->value呢?

// 3.2. OBJ_ENCODING_HT 的 field -> value 的添加
if (o->encoding == OBJ_ENCODING_HT) {
// hash 表中查找对应的 field
dictEntry *de = dictFind(o->ptr,field);
if (de) {
sdsfree(dictGetVal(de));
// hset 时使用 HASH_SET_COPY, 所以直接使用 sdsdup() 即可
if (flags & HASH_SET_TAKE_VALUE) {
dictGetVal(de) = value;
value = NULL;
} else {
dictGetVal(de) = sdsdup(value);
}
update = ;
} else {
// 新增 field -> value
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);
}
// 添加到 hash 表中,前些篇章讲解过,大概就是计算hash,放入v的过程
dictAdd(o->ptr,f,v);
}
}

如此看来,OBJ_ENCODING_HT 的实现反而简单了哦。

总结下 hash的插入过程,hash 初始创建时都是使用ziplist 进行容纳元素的,在特定情况下会触发 ziplist 为 ht 的编码方式, 比如:

    1. hset时自身的参数大于设置值(默认: 64)时直接转换 ziplist -> ht;

    2. hash表的元素数量大于设置值(默认: 512)时转换 ziplist -> ht;

  这么设计的原因是,元素较少且占用空间较小时,使用ziplist会节省空间,且时间消耗与hash表相关并不大,所以 ziplist 是优先的选择了。但是大量数据还是必须要使用hash表存储的。

Redis系列(九):数据结构Hash源码解析和HSET、HGET命令的更多相关文章

  1. 老生常谈系列之Aop--Spring Aop源码解析(二)

    老生常谈系列之Aop--Spring Aop源码解析(二) 前言 上一篇文章老生常谈系列之Aop--Spring Aop源码解析(一)已经介绍完Spring Aop获取advice切面增强方法的逻辑, ...

  2. 老生常谈系列之Aop--Spring Aop源码解析(一)

    老生常谈系列之Aop--Spring Aop源码解析(一) 前言 上一篇文章老生常谈系列之Aop--Spring Aop原理浅析大概阐述了动态代理的相关知识,并且最后的图给了一个Spring Aop实 ...

  3. Redis系列(十):数据结构Set源码解析和SADD、SINTER、SDIFF、SUNION、SPOP命令

    1.介绍 Hash是以K->V形式存储,而Set则是K存储,空间节省了很多 Redis中Set是String类型的无序集合:集合成员是唯一的. 这就意味着集合中不能出现重复的数据.可根据应用场景 ...

  4. Java 集合系列Stack详细介绍(源码解析)和使用示例

    Stack简介 Stack是栈.它的特性是:先进后出(FILO, First In Last Out). java工具包中的Stack是继承于Vector(矢量队列)的,由于Vector是通过数组实现 ...

  5. 小学徒成长系列—StringBuilder & StringBuffer关键源码解析

    在前面的博文<小学徒成长系列—String关键源码解析>和<小学徒进阶系列—JVM对String的处理>中,我们讲到了关于String的常用方法以及JVM对字符串常量Strin ...

  6. ThreadPoolExecutor系列<三、ThreadPoolExecutor 源码解析>

    本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681826.html 在源码解析前,需要先理清线程池控制的运行状态 ...

  7. Raft协议实战之Redis Sentinel的选举Leader源码解析

    这可能是我看过的写的最详细的关于redis 选举的文章了, 原文链接 Raft协议是用来解决分布式系统一致性问题的协议,在很长一段时间,Paxos被认为是解决分布式系统一致性的代名词.但是Paxos难 ...

  8. 【OpenStack】OpenStack系列13之Nova源码解析与API扩展

    学习思路 议程:代码结构-主干流程-分层架构-业务模型-数据库模型-消息模型 分布式架构:Api:横向扩展    rpc:纵向扩展 分层架构:Controller接口层.View/Manager逻辑层 ...

  9. Redis源码解析:10scan类命令的实现

    像keys或者smembers命令,需要遍历数据集合中的所有元素.在一个大的数据库中使用,可能会阻塞服务器较长的一段时间,造成性能问题,因此不适用与生产环境. 在Redis2.8.0中引入了scan类 ...

随机推荐

  1. Java实现 LeetCode 811 子域名访问计数 (暴力)

    811. 子域名访问计数 一个网站域名,如"discuss.leetcode.com",包含了多个子域名.作为顶级域名,常用的有"com",下一级则有" ...

  2. Java实现 LeetCode 749 隔离病毒(DFS嵌套)

    749. 隔离病毒 病毒扩散得很快,现在你的任务是尽可能地通过安装防火墙来隔离病毒. 假设世界由二维矩阵组成,0 表示该区域未感染病毒,而 1 表示该区域已感染病毒.可以在任意 2 个四方向相邻单元之 ...

  3. Java实现 LeetCode 662 二叉树最大宽度(递归)

    662. 二叉树最大宽度 给定一个二叉树,编写一个函数来获取这个树的最大宽度.树的宽度是所有层中的最大宽度.这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空. 每一层的 ...

  4. Java实现 LeetCode 507 完美数

    507. 完美数 对于一个 正整数,如果它和除了它自身以外的所有正因子之和相等,我们称它为"完美数". 给定一个 整数 n, 如果他是完美数,返回 True,否则返回 False ...

  5. Java实现 蓝桥杯VIP 算法训练 sign函数

    试题 算法训练 sign函数 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 给定实数x,输出sign(x)的值. sign(x)是符号函数,如果x>0,则返回1:如果x=0, ...

  6. java实现第六届蓝桥杯胡同门牌号

    胡同门牌号 小明家住在一条胡同里.胡同里的门牌号都是连续的正整数,由于历史原因,最小的号码并不是从1开始排的. 有一天小明突然发现了有趣的事情: 如果除去小明家不算,胡同里的其它门牌号加起来,刚好是1 ...

  7. Linux RPM管理命令

    RPM安装 rpm -ivh 包全名,其中,-i 表示安装,-v 表示显示详细信息,-h 表示显示进度 手动安装会有很多包依赖需要解决,如果是模块依赖,可以使用rpmfind进行查询解决 RPM升级 ...

  8. Python性能分析工具

    import cProfile import pstats from flask import Flask,jsonify, request @app.route("/test", ...

  9. Spring Cloud 系列之 Alibaba Nacos 注册中心(一)

    前言 从本章节开始,我们学习 Spring Cloud Alibaba 相关微服务组件. Spring Cloud Alibaba 介绍 Spring Cloud Alibaba 致力于提供微服务开发 ...

  10. DOM 元素的循环遍历

    ​博客地址:https://ainyi.com/89​ 获取 DOM 元素的几种方式 get 方式: getElementById getElementsByTagName getElementsBy ...