zoukankan      html  css  js  c++  java
  • Redis(五):hash/hset/hget 命令源码解析

      Redis作为nosql数据库,kv string型数据的支持是最基础的,但是如果仅有kv的操作,也不至于有redis的成功。(memcache就是个例子)

      Redis除了string, 还有hash,list,set,zset。

      所以,我们就来看看hash的相关操作实现吧。

      首先,我们从作用上理解hash存在的意义:Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。从另一个方面来说是,hash可以聚合很多类似的属性,这是string中难以实现的。

    所以,总体来说,hash的命令与string的命令差不太多。其操作手册如下:

    1> hdel 命令:删除一个或多个哈希表字段
    格式:HDEL key field2 [field2]
    返回值:被成功删除字段的数量,不包括被忽略的字段。

    2> hexists 命令:查看哈希表 key 中,指定的字段是否存在
    格式:HEXISTS key field
    返回值:如果哈希表含有给定字段,返回 1 。 如果哈希表不含有给定字段,或 key 不存在,返回 0 。

    3> hget 命令:获取存储在哈希表中指定字段的值
    格式:HGET key field
    返回值:返回给定字段的值。如果给定的字段或 key 不存在时,返回 nil 。

    4> hgetall 命令:获取在哈希表中指定 key 的所有字段和值
    格式:HGETALL key
    返回值:以列表形式返回哈希表的字段及字段值。 若 key 不存在,返回空列表。

    5> hincrby 命令:为哈希表 key 中的指定字段的整数值加上增量 increment
    格式:HINCRBY key field increment
    返回值:执行 HINCRBY 命令之后,哈希表中字段的值。

    6> hincrbyfloat 命令:为哈希表 key 中的指定字段的浮点数值加上增量 increment
    格式:HINCRBYFLOAT key field increment
    返回值:执行 Hincrbyfloat 命令之后,哈希表中字段的值。

    7> hkeys 命令:获取所有哈希表中的字段
    格式:HKEYS key
    返回值:包含哈希表中所有字段的列表。 当 key 不存在时,返回一个空列表。

    8> hlen 命令:获取哈希表中字段的数量
    格式:HLEN key
    返回值:哈希表中字段的数量。 当 key 不存在时,返回 0 。

    9> hmget 命令:获取所有给定字段的值
    格式:HMGET key field1 [field2]
    返回值:一个包含多个给定字段关联值的表,表值的排列顺序和指定字段的请求顺序一样。

    10> hmset 命令:同时将多个 field-value (域-值)对设置到哈希表 key 中
    格式:HMSET key field1 value1 [field2 value2 ]
    返回值:如果命令执行成功,返回 OK 。

    11> hset 命令:将哈希表 key 中的字段 field 的值设为 value
    格式:HSET key field value
    返回值:如果字段是哈希表中的一个新建字段,并且值设置成功,返回 1 。 如果哈希表中域字段已经存在且旧值已被新值覆盖,返回 0 。

    12> hsetnx 命令:只有在字段 field 不存在时,设置哈希表字段的值
    格式:HSETNX key field value
    返回值:设置成功,返回 1 。 如果给定字段已经存在且没有操作被执行,返回 0 。

    13> hvals 命令:获取哈希表中所有值
    格式:HVALS key
    返回值:一个包含哈希表中所有值的表。 当 key 不存在时,返回一个空表。

    14> hscan 命令:迭代哈希表中的键值对
    格式:HSCAN key cursor [MATCH pattern] [COUNT count]

      其中,有的是单kv操作有的是指量操作,有的是写操作有的是读操作。从实现上看,大体上很多命令是类似的:

      比如: hset/hmset/hincrbyXXX 可以是一类的

      比如:hget/hgetall/hexists/hkeys/hmget 可以是一类

      注意:以上分法仅是为了让我们看清本质,对实际使用并无实际参考意义。

    所以,我们就挑几个方法来解析下 hash 的操作实现吧。

    零、hash数据结构


      hash相关的命令定义如下:

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

      ziplist 数据结构

    typedef struct zlentry {
        unsigned int prevrawlensize, prevrawlen;
        unsigned int lensize, len;
        unsigned int headersize;
        unsigned char encoding;
        unsigned char *p;
    } zlentry;
    #define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))
    #define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
    #define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
    #define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
    #define ZIPLIST_END_SIZE        (sizeof(uint8_t))
    #define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)
    #define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
    #define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
        

      hashtable 数据结构:

    typedef struct dict {
        dictType *type;
        void *privdata;
        dictht ht[2];
        long rehashidx; /* rehashing not in progress if rehashidx == -1 */
        unsigned long iterators; /* number of iterators currently running */
    } dict;
    typedef struct dictht {
        dictEntry **table;
        unsigned long size;
        unsigned long sizemask;
        unsigned long used;
    } dictht;
    typedef struct dictEntry {
        void *key;
        void *val;
        struct dictEntry *next;
    } dictEntry;

    一、hset 设置单个 field -> value


      “增删改查”中的“增改” 就是它了。

    // t_hash.c, set key field value
    void hsetCommand(client *c) {
        int update;
        robj *o;
        // 1. 查找hash的key是否存在,不存在则新建一个,然后在其上进行数据操作
        if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
        // 2. 检查2-3个参数是否需要将简单版(ziplist)hash表转换为复杂的hash表,转换后的表通过 o->ptr 体现
        hashTypeTryConversion(o,c->argv,2,3);
        // 3. 添加kv到 o 的hash表中
        update = hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY);
        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++;
    }
    
    // 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", 3, ZIPLIST_TAIL);
        zl = ziplistPush(zl, (unsigned char*)"quux", 4, ZIPLIST_TAIL);
        zl = ziplistPush(zl, (unsigned char*)"hello", 5, ZIPLIST_HEAD);
        zl = ziplistPush(zl, (unsigned char*)"1024", 4, 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, 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 */
            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 = 0;
        if (index < 0) {
            // 小于0时,反向查找
            index = (-index)-1;
            p = ZIPLIST_ENTRY_TAIL(zl);
            if (p[0] != ZIP_END) {
                ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
                while (prevlen > 0 && index--) {
                    p -= prevlen;
                    ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
                }
            }
        } else {
            p = ZIPLIST_ENTRY_HEAD(zl);
            while (p[0] != ZIP_END && index--) {
                p += zipRawEntryLength(p);
            }
        }
        // 迭代完成还没找到元素 p[0]=ZIP_END
        // index 超出整体ziplist大小则遍历完成后 index>0
        return (p[0] == ZIP_END || index > 0) ? 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[0] == ZIP_END) {
            return NULL;
        }
        // 当前指针偏移当前元素长度(根据ziplist协议),即到下一元素指针位置
        p += zipRawEntryLength(p);
        if (p[0] == 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[0] == ZIP_END) return 0;
        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 1;
    }
    // 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) == 1) {                                                  
            (prevlen) = (ptr)[0];                                                  
        } else if ((prevlensize) == 5) {                                           
            assert(sizeof((prevlensize)) == 4);                                    
            // 当ptr[0]>254时,代表内容有点大,需要使用 5个字符保存上一字符长度
            memcpy(&(prevlen), ((char*)(ptr)) + 1, 4);                             
            memrev32ifbe(&prevlen);                                                
        }                                                                          
    } while(0);
    /* 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)[0] < ZIP_BIGLEN) {                                               
            (prevlensize) = 1;                                                     
        } else {                                                                   
            (prevlensize) = 5;                                                     
        }                                                                          
    } while(0);
    /* 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) = 1;                                                     
                (len) = (ptr)[0] & 0x3f;                                           
            } 
            // 1 << 6 =64
            else if ((encoding) == ZIP_STR_14B) {                                  
                (lensize) = 2;                                                     
                (len) = (((ptr)[0] & 0x3f) << 8) | (ptr)[1];                       
            }
            // 2 << 6 =128
            else if (encoding == ZIP_STR_32B) {                                    
                (lensize) = 5;                                                     
                (len) = ((ptr)[1] << 24) |                                         
                        ((ptr)[2] << 16) |                                         
                        ((ptr)[3] <<  8) |                                         
                        ((ptr)[4]);                                                
            } else {                                                               
                assert(NULL);                                                      
            }                                                                      
        } else {                                                                   
            // 超过 0xc0 的长度了,直接使用 1,2,3,4 表示len
            (lensize) = 1;                                                         
            (len) = zipIntSize(encoding);                                          
        }                                                                          
    } while(0);
    /* Extract the encoding from the byte pointed by 'ptr' and set it into
     * 'encoding'. */
    #define ZIP_ENTRY_ENCODING(ptr, encoding) do {  
        (encoding) = (ptr[0]); 
        if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; 
    } while(0)
    
    /* 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
    View Code

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

    // 3. 添加kv到 hash表中, 稍微复杂
    // t_hash.c, 做变更到hash表中
    int hashTypeSet(robj *o, sds field, sds value, int flags) {
        int update = 0;
        // 针对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), 1);
                if (fptr != NULL) {
                    /* Grab pointer to the value (fptr points to the field) */
                    // value 不可以为null, 否则 ziplist 将无法工作
                    vptr = ziplistNext(zl, fptr);
                    serverAssert(vptr != NULL);
                    update = 1;
    
                    /* 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 = 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. */
        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 = 0;
        unsigned char vencoding = 0;
        long long vll = 0;
    
        while (p[0] != 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 == 0) {
                /* Compare current entry with specified entry */
                // 针对不同的编码使用不同的比较方式
                if (ZIP_IS_STR(encoding)) {
                    // 找到相应的元素,直接返回 p 指针
                    if (len == vlen && memcmp(q, vstr, vlen) == 0) {
                        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 == 0) {
                        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 = 0;
        size_t offset;
        int nextdiff = 0;
        unsigned char encoding = 0;
        long long value = 123456789; /* 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[0] != ZIP_END) {
            ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
        } else {
            unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
            if (ptail[0] != 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[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
    
        /* 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[0] != ZIP_END) {
            /* Subtract one because of the ZIP_END bytes */
            // 字符拷贝
            memmove(p+reqlen,p-nextdiff,curlen-offset-1+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 != 0) {
            // 如果本次更新后数据位置变化,则需要更新后续的元素位置
            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,1);
        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好像没那么简单呢,为啥还要搞这么复杂呢?其实以上代码,仅是在人看来复杂,对机器来说就是更多的移位计算操作,多消耗点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 = 1;
            } 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表存储的。

    二、hmset 批量添加元素


      hset 和 hmset 在实现上基本如出一辙,所以简单瞅瞅就得了。

    // t_hash.c, hmset key f1 v1 f2 v2
    void hmsetCommand(client *c) {
        int i;
        robj *o;
        // 参数个数检查,必定是2n
        if ((c->argc % 2) == 1) {
            addReplyError(c,"wrong number of arguments for HMSET");
            return;
        }
        // 插入方式与 hset 一毛一样,差别在于批量插入时,会循环向 key-hash表中添加field->value
        if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
        hashTypeTryConversion(o,c->argv,2,c->argc-1);
        // 循环insert
        for (i = 2; i < c->argc; i += 2) {
            hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);
        }
        addReply(c, shared.ok);
        signalModifiedKey(c->db,c->argv[1]);
        notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
        server.dirty++;
    }

    三、hget 获取某字段值


      这种命令的时间复杂度都是 O(1), 所以一般是简单至上。

    // t_hash.c    
    void hgetCommand(client *c) {
        robj *o;
        // 查找key, 不存在或者类型不一致则直接返回
        if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
            checkType(c,o,OBJ_HASH)) return;
        // 基于o, 返回 field 对应的元素值即可
        addHashFieldToReply(c, o, c->argv[2]->ptr);
    }
    // t_hash.c
    static void addHashFieldToReply(client *c, robj *o, sds field) {
        int ret;
    
        if (o == NULL) {
            addReply(c, shared.nullbulk);
            return;
        }
    
        if (o->encoding == OBJ_ENCODING_ZIPLIST) {
            unsigned char *vstr = NULL;
            unsigned int vlen = UINT_MAX;
            long long vll = LLONG_MAX;
            // 基于 ziplist, 
            ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
            if (ret < 0) {
                // 响应为空
                addReply(c, shared.nullbulk);
            } else {
                // 添加到输出缓冲
                if (vstr) {
                    addReplyBulkCBuffer(c, vstr, vlen);
                } else {
                    addReplyBulkLongLong(c, vll);
                }
            }
    
        } else if (o->encoding == OBJ_ENCODING_HT) {
            // hash 表类型则查找 hash 表即可
            sds value = hashTypeGetFromHashTable(o, field);
            // 添加到输出缓冲
            if (value == NULL)
                // 响应为空
                addReply(c, shared.nullbulk);
            else
                addReplyBulkCBuffer(c, value, sdslen(value));
        } else {
            serverPanic("Unknown hash encoding");
        }
    }
    // t_hash.c, 从 ziplist 中查找 field 值
    /* Get the value from a ziplist encoded hash, identified by field.
     * Returns -1 when the field cannot be found. */
    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);
            if (fptr != NULL) {
                /* Grab pointer to the value (fptr points to the field) */
                vptr = ziplistNext(zl, fptr);
                serverAssert(vptr != NULL);
            }
        }
    
        if (vptr != NULL) {
            ret = ziplistGet(vptr, vstr, vlen, vll);
            serverAssert(ret);
            return 0;
        }
    
        return -1;
    }
    
    // t_hash.c, 从hash表中查找 field 字段的值
    /* 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. */
    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);
    }

    四、hmget 批量获取值


      与hget如出一辙。

    // t_hash.c
    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. */
        o = lookupKeyRead(c->db, c->argv[1]);
        if (o != NULL && o->type != OBJ_HASH) {
            addReply(c, shared.wrongtypeerr);
            return;
        }
        // 循环输出值
        addReplyMultiBulkLen(c, c->argc-2);
        for (i = 2; i < c->argc; i++) {
            addHashFieldToReply(c, o, c->argv[i]->ptr);
        }
    }

    五、hgetall 获取所有hash的kv


      hgetall 和 hmget 方式稍微有点不一样,原因是为了让 hkeysCommand/hvalsCommand 进行复用。

    // t_hash.c
    void hgetallCommand(client *c) {
        genericHgetallCommand(c,OBJ_HASH_KEY|OBJ_HASH_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) {
                addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY);
                count++;
            }
            if (flags & OBJ_HASH_VALUE) {
                addHashIteratorCursorToReply(c, hi, OBJ_HASH_VALUE);
                count++;
            }
        }
    
        hashTypeReleaseIterator(hi);
        serverAssert(count == length);
    }
    static void addHashIteratorCursorToReply(client *c, hashTypeIterator *hi, int what) {
        if (hi->encoding == OBJ_ENCODING_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) {
            sds value = hashTypeCurrentFromHashTable(hi, what);
            addReplyBulkCBuffer(c, value, sdslen(value));
        } else {
            serverPanic("Unknown hash encoding");
        }
    }

    六、hincrby 增加x某字段


      hincrby key field 1

    // t_hash.c, 
    void hincrbyCommand(client *c) {
        long long value, incr, oldvalue;
        robj *o;
        sds new;
        unsigned char *vstr;
        unsigned int vlen;
        // 解析增加字段值到 incr 中
        if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;
        // 获取原值或者设置为0
        if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
        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 */
        } else {
            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;
        }
        // 将相加后的值重置设置回hash表中
        value += incr;
        new = sdsfromlonglong(value);
        hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
        addReplyLongLong(c,value);
        signalModifiedKey(c->db,c->argv[1]);
        notifyKeyspaceEvent(NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
        server.dirty++;
    }

    七、hdel 删除某字段


      hdel key field

    // t_hash.c, 
    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;
        // 循环删除给定字段列表
        for (j = 2; j < c->argc; j++) {
            if (hashTypeDelete(o,c->argv[j]->ptr)) {
                deleted++;
                // 当没有任何元素后,直接将key删除
                if (hashTypeLength(o) == 0) {
                    dbDelete(c->db,c->argv[1]);
                    keyremoved = 1;
                    break;
                }
            }
        }
        if (deleted) {
            signalModifiedKey(c->db,c->argv[1]);
            notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
            if (keyremoved)
                notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
                                    c->db->id);
            server.dirty += deleted;
        }
        addReplyLongLong(c,deleted);
    }
    // 具体删除 field, 同样区分编码类型,不同处理逻辑
    /* Delete an element from a hash.
     * Return 1 on deleted and 0 on not found. */
    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) {
                // ziplist 删除,依次删除 field, value
                fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1);
                if (fptr != NULL) {
                    // ziplistDelete 为原地删除,所以只要调用2次,即把kv删除
                    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;
    
                /* Always check if the dictionary needs a resize after a delete. */
                // hash 删除的,可能需要进行缩容操作,这种处理方法相对特殊些
                if (htNeedsResize(o->ptr)) dictResize(o->ptr);
            }
    
        } else {
            serverPanic("Unknown hash encoding");
        }
        return deleted;
    }
    // server.c, 是否需要进行 resize
    int htNeedsResize(dict *dict) {
        long long size, used;
    
        size = dictSlots(dict);
        used = dictSize(dict);
        // HASHTABLE_MIN_FILL=10, 即使用率小于 1/10 时,可以进行缩容操作了
        return (size && used && size > DICT_HT_INITIAL_SIZE &&
                (used*100/size < HASHTABLE_MIN_FILL));
    }
        

      至此,整个hash数据结构的解析算是完整了。总体来说,hash由两种数据结构承载,ziplist在小数据量时使用,稍微复杂,但对于昂贵的内存来说是值得的。hash表在数据量大时使用,容易理解。通过本文的讲解,相信可以验证了你对redis hash 的实现的猜想了。

  • 相关阅读:
    LeetCode——Generate Parentheses
    LeetCode——Best Time to Buy and Sell Stock IV
    LeetCode——Best Time to Buy and Sell Stock III
    LeetCode——Best Time to Buy and Sell Stock
    LeetCode——Find Minimum in Rotated Sorted Array
    Mahout实现基于用户的协同过滤算法
    使用Java对文件进行解压缩
    LeetCode——Convert Sorted Array to Binary Search Tree
    LeetCode——Missing Number
    LeetCode——Integer to Roman
  • 原文地址:https://www.cnblogs.com/yougewe/p/12234983.html
Copyright © 2011-2022 走看看