redis中zipmap源码剖析

tree_sitter多语言解析器配置实战指南 本文是一份详细的Tree-sitter多语言解析器配置实战指南。文章从零开始,手把手指导如何搭建Python环境、安装指定版本的tree-sitter库、下载并编译多种语言的语法规则,最终构建一个统一的解析器库。通过实战代码演示了如何初始化解析器、解析代码并遍历语法树,旨在帮助开发者高效处理多语言代码分析任务,并分享了高级技巧与常见问题排坑经验。 阅读详情

最近看了一篇剖析redis中zipmap源码的好文,特意转载保留供将来长期回顾

转载自:http://blog.youkuaiyun.com/Xiejingfa/article/details/51111230

今天为大家带来Redis中zipmap数据结构的分析,该结构定义在zipmap.h和zipmap.c文件中。我把zipmap称作“压缩字典”(不知道这样称呼正不正确)是因为zipmap利用字符串实现了一个简单的hash_table结构,又通过固定的字节表示节省空间。zipmap和前面介绍的ziplist结构十分类似,我们可以对比地进行学习:

  • Redis中定义了双向链表list,但是这种链表的每一个节点需要存储两个指针来指向前一个节点和下一个节点,存在着空间利用率不高的缺点(特别是在存储少量数据的情况下)。为了解决这个问题,Redis引入了压缩列表ziplist,通过存储上一个节点长度和当前节点长度从而在大多数情况下节省了链表指针的存储空间开销。

  • Redis中定义了字典dict来实现很多高级语言中的Map结构,这种结构相对来说比较复杂,而且需要两张散列表来实现rehash操作,在存储少量元素的时候空间利用率也比较低。为此,Redis引入了压缩字典zipmap,就是我们今天要介绍的数据结构。zipmap实质上是用一个字符串来依次保存key和value,有良好的空间利用率和相对不错的查找速度(下面我们会详细讲解)。

如果你还不知道什么是ziplist和dict,请参看我前面的博文:

Redis是基于内存的高效的数据库,从list到ziplist,从dict到zipmap,我们可以看到Redis的作者们为了高效地利用内存空间真是无所不用其极。下面我们就进入今天的主题,介绍zipmap的实现。

再次强调,zipmap和ziplist有很多相似的地方,请大家注意比对学习。


1、zipmap的存储结构

zipmap是用连续内存保存key-value对的结构,查询时是依次遍列每个key-value对,直到查到为止。其存储结构如下所示:

这里写图片描述

各部分的含义如下:

(1)、zmlen字段

zmlen是1个字节的无符号整型数,表示zipmap当前的键值对数量,最多只能表示253个键值对。当zipmap中的元素数量大于或等于254时,只能通过遍历一遍zipmap来确定其大小。

(2)、key length和value length字段

这两个length字段的编码方式和ziplist有些类似,可以用1个字节或5个字节编码表示,具体如下:

  • 如果随后的字符串(key或value)长度小于或等于253,直接用1个字节表示其长度。
  • 如果随后的字符串(key或value)长度超过254,则用5个字节表示,其中第一个字节值为254,接下来的4个字节才是字符串长度。

(3)、free字段

free表示随后的value后面的空闲字节数。比如:假设zipmap存在”foo” => “bar”这样一个键值对,随后我们将“bar”设置为“hi”,此时free = 1,表示value字符串后面有1个字节大小的空闲空间。

free字段是一个占1个字节的整型数,它的值一般都比较小,如果空闲区间太大,zipmap会进行调整以使整个map尽可能小,这个过程发生在zipmapSet操作中。

(4)、end字段

end是zipmap的结尾符,占用1个字节,其值为255。

从zipmap的存储结构中我们可以看到,zipmap中的键值对就是顺序存放的key和value字符串,中间加入了相关的字符串长度编码信息以方便我们获取相关的key和value。而整个zipmap就是在一块内存区中依次存放key-value字符串。

2、zipmap的几个重要操作

与ziplist相比,zipmap的操作要简单许多,Redis甚至没有定义额外的结构体,只是通过简单的字符串操作来维护一个zipmap结构。下面我们就来介绍一下zipmap相关的几个操作:

2.1、创建一个空zipmap

一个空的zipmap结构只有两个字节(即zmlen字段和end字段),结构如下:

这里写图片描述

创建空的zipmap很简单,这里拿出来讲是为了让大家更好地理解zipmap的存储结构。

unsigned char *zipmapNew(void) {
    // 初始化时只有2个字节,第1个字节表示zipmap保存的key-value对的个数,第2个字节为结尾符
    unsigned char *zm = zmalloc(2);

    // 当前保存的键值对个数为0
    zm[0] = 0; /* Length */
    zm[1] = ZIPMAP_END;
    return zm;
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

所以zipmap是不是很简单?

2.2、查找操作

作为一个hash_table结构,其最主要的操作是根据给定的key值查找相应的value值。在zipmap中,key-value是顺序存放的,所以只能从前往后遍历以查找目标键值对,但由于每个键值对中都记录了key和value的长度,所以查找起来也很方便。

具体如下所示:

 /* 按关键字key查找zipmap,如果totlen不为NULL,函数返回后存放zipmap占用的字节数 */
static unsigned char *zipmapLookupRaw(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned int *totlen) {
    // zipmap中第1个字节是zmlen字段,zm+1跳过第1个字节
    unsigned char *p = zm+1, *k = NULL;
    unsigned int l,llen;

    // 从前往后查找
    while(*p != ZIPMAP_END) {
        unsigned char free;

        /* Match or skip the key */
        // 确定key字符串的长度
        l = zipmapDecodeLength(p);
        // 确定保存key字符串长度所需要的字节数,也就是len字段所需要的字节数
        llen = zipmapEncodeLength(NULL,l);
        // 比较当前key与给定key是否匹配
        if (key != NULL && k == NULL && l == klen && !memcmp(p+llen,key,l)) {
            /* Only return when the user doesn't care
             * for the total length of the zipmap. */
            // 如果totlen为NULL,表示函数调用者不关心zipmap占用的字节数,此时直接返回p,否则先记录下p指针然后继续遍历
            if (totlen != NULL) {
                k = p;
            } else {
                return p;
            }
        }
        // p加上llen和l,到了value节点处
        p += llen+l;
        /* Skip the value as well */
        // 确定value字符串的长度
        l = zipmapDecodeLength(p);
        // 确定保存value字符串长度所需要的字节数,也就是len字段所需要的字节数
        p += zipmapEncodeLength(NULL,l);
        // 读出free字段的值(前面我们讲过:free只占用一个字节)
        free = p[0];
        // 跳到下一个key节点的
        p += l+1+free; /* +1 to skip the free byte */
    }
    // 到这里遍历完整个zipmap,得到其占用的字节数
    if (totlen != NULL) *totlen = (unsigned int)(p-zm)+1;
    return k;
}
 
  • 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
2.3、插入 & 更新操作

zipmap将插入和更新操作实现在同一个函数zipmapSet中,即存在指定key的键值对则执行更新操作,否则执行插入操作。

函数zipmapSet的执行流程如下:

  • 调用zipmapLookupRaw函数确定指定key的键值对是否已经在zipmap中。
  • 重新分配空间以确保有足够的空间来容纳新的键值对(插入操作)或新的value值(更新操作)。如果是插入操作,就在zipmap尾部增加空间,如果是更新操作,则通过memmove移动在原键值对处留出足够的空间。
  • 在前面两步中已经确定了键值对的位置并有足够的空间,直接写入或更新键值对的信息即可。
/* 根据key设置value,如果key不存在则创建相应的键值对,参数update用来辨别更新操作和添加操作。 */
unsigned char *zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char *val, unsigned int vlen, int *update) {
    unsigned int zmlen, offset;
    // 计算存储key和value所需要的字节数
    unsigned int freelen, reqlen = zipmapRequiredLength(klen,vlen);
    unsigned int empty, vempty;
    unsigned char *p;

    /************************************************************************
     *  下面这段代码用于在zipmap留出足够的空间来容纳新插入的键值对或新的value值,尚未写入
     ************************************************************************/
    freelen = reqlen;
    if (update) *update = 0;
    // 在zipmap中查找key,函数返回后zmlen中保存了zipmap所占用的字节数。
    p = zipmapLookupRaw(zm,key,klen,&zmlen);
    if (p == NULL) {
        /* Key not found: enlarge */
        // 如果key指定的键值对不存在,则对zipmap扩容,为容纳新的键值对准备内存空间
        // zipmapResize执行的是realloc操作
        zm = zipmapResize(zm, zmlen+reqlen);
        // 此时p指向扩容前zipmap的结尾符,将从这里添加新的键值对
        p = zm+zmlen-1;
        // 更新zipmap所占用的内存空间大小
        zmlen = zmlen+reqlen;

        /* Increase zipmap length (this is an insert) */
        // 更新zipmap中保存的键值对数量,即zmlen字段
        if (zm[0] < ZIPMAP_BIGLEN) zm[0]++;

    } else {
        /* Key found. Is there enough space for the new value? */
        /* 找到可对应的键值对,执行更新操作。这里需要考虑value节点的空间大小是否能够容纳新值 */
        /* Compute the total length: */
        if (update) *update = 1;
        // 求出旧value节点的空间大小
        freelen = zipmapRawEntryLength(p);
        if (freelen < reqlen) {
            /* Store the offset of this key within the current zipmap, so
             * it can be resized. Then, move the tail backwards so this
             * pair fits at the current position. */
             // 旧节点的空间太小,需要扩容操作,zipmapResize函数会重新分配空间,所以需要记录p指针的偏移量
            offset = p-zm;
            zm = zipmapResize(zm, zmlen-freelen+reqlen);
            p = zm+offset;

            /* The +1 in the number of bytes to be moved is caused by the
             * end-of-zipmap byte. Note: the *original* zmlen is used. */
            // 移动旧value节点以后的元素以确保有足够的空间容纳新值( +1是将尾部结尾符一起移动)
            memmove(p+reqlen, p+freelen, zmlen-(offset+freelen+1));
            zmlen = zmlen-freelen+reqlen;
            freelen = reqlen;
        }
    }

    /* We now have a suitable block where the key/value entry can
     * be written. If there is too much free space, move the tail
     * of the zipmap a few bytes to the front and shrink the zipmap,
     * as we want zipmaps to be very space efficient. */
    // freelen表示经上步骤后流出来的空余空间大小,reqlen表示插入或更新键值对所需要的空间,两者的差就是free字段的
    // 的值,如果该值过大zipmap会自动调整。下面这段代码就是完成调整功能。
    empty = freelen-reqlen;
    if (empty >= ZIPMAP_VALUE_MAX_FREE) {
        /* First, move the tail <empty> bytes to the front, then resize
         * the zipmap to be <empty> bytes smaller. */
        offset = p-zm;
        memmove(p+reqlen, p+freelen, zmlen-(offset+freelen+1));
        zmlen -= empty;
        zm = zipmapResize(zm, zmlen);
        p = zm+offset;
        vempty = 0;
    } else {
        vempty = empty;
    }

    /******************************************
     *  下面的操作是讲key和value写入zipmap指定位置
     *******************************************/
    /* Just write the key + value and we are done. */
    /* Key: */
    // 对key的长度编码并写入zipmap中
    p += zipmapEncodeLength(p,klen);
    // 写入key字符串
    memcpy(p,key,klen);
    // 移动指针到value写入位置
    p += klen;
    /* Value: */
    // 对value的长度编码并写入zipmap中
    p += zipmapEncodeLength(p,vlen);
    // 写入free字段
    *p++ = vempty;
    // 写入value
    memcpy(p,val,vlen);
    return zm;
}
 
  • 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

3、总结

zipmap主要是为了节省内存空间而设计的字符串-字符串映射结构(哈希结构)。它的实现很简单,代码只有400余行。

zipmap通过在一块连续的内存空间上依次存放key-value对来维持key和value的映射结构,但它跟我们传统意义上的散列结构不同,只能通过依次遍历每个key-value节点来查找给定key值得value信息。但是,相比于字典dict,zipmap通过固定的字节编码表示节省了不少空间。另外,由于使用了一块连续的内存空间,zipmap的每一次插入、删除、更新操作都有可能造成空间的重新分配。

从zipmap的实现上我们可以看到,zipmap不适合用来存放大量的key-value对。在运用上,Redis中内置的Hash结构在保存的元素数量较少时会采用zipmap来存放键值对,当元素的数量到达给定值后才会转为用哈希表来存储以节省内存。


下面贴出注释版的代码,供大家参考:

/* String -> String Map data structure optimized for size.
 *  zipmap是为了节省内存空间而设计的字符串-字符串映射结构(哈希结构)
 * 
 * This file implements a data structure mapping strings to other strings
 * implementing an O(n) lookup data structure designed to be very memory
 * efficient.
 *  zipmap实现了一种将字符串与字符串之间映射起来的数据结构,它支持O(n)的查找效率并具有良好的空间利用率。
 *
 * The Redis Hash type uses this data structure for hashes composed of a small
 * number of elements, to switch to a hash table once a given number of
 * elements is reached.
 *  Redis中内置的Hash结构在保存的元素数量较少时会采用zipmap来存放键值对,当元素的数量到达给定值
 *  后才会转为用哈希表来存储以节省内存。
 *
 * Given that many times Redis Hashes are used to represent objects composed
 * of few fields, this is a very big win in terms of used memory.
 *  Redis中的Hash结构经常被用来保存只有少量字段的对象,这种情况下使用zipmap能很大程度上节省内存
 *
 * --------------------------------------------------------------------------
 *
 * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Redis nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/* Memory layout of a zipmap, for the map "foo" => "bar", "hello" => "world":
 *  zipmap的内存空间布局如下(假设存放着"foo" => "bar", "hello" => "world"数据):
 *
 * <zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"
 *
 * <zmlen> is 1 byte length that holds the current size of the zipmap.
 * When the zipmap length is greater than or equal to 254, this value
 * is not used and the zipmap needs to be traversed to find out the length.
 *  zmlen是1个字节的无符号整型数,表示zipmap当前的键值对数量,最多只能表示253个键值对。当zipmap中的元素数量大于
 *  或等于254时,只能通过遍历一遍zipmap来确定其大小。
 *
 * <len> is the length of the following string (key or value).
 *  len表示随后的字符串的长度,这些字符串可能是key或者value
 *
 * <len> lengths are encoded in a single value or in a 5 bytes value.
 * If the first byte value (as an unsigned 8 bit value) is between 0 and
 * 253, it's a single-byte length. If it is 254 then a four bytes unsigned
 * integer follows (in the host byte ordering). A value of 255 is used to
 * signal the end of the hash.
 *  len字段的编码方式和ziplist有些类似,可以用1个字节或5个字节编码表示,具体如下:
 *      (1)如果随后的字符串长度小于或等于253,直接用1个字节表示其长度
 *      (2)如果随后的字符串长度超过254,则用5个字节表示,其中第一个字节值为254,接下来的4个字节才是字符串长度
 *
 * <free> is the number of free unused bytes after the string, resulting
 * from modification of values associated to a key. For instance if "foo"
 * is set to "bar", and later "foo" will be set to "hi", it will have a
 * free byte to use if the value will enlarge again later, or even in
 * order to add a key/value pair if it fits.
 *  free表示随后的value的空闲字节数。比如:假设zipmap存在"foo" => "bar"这样一个键值对,随后我们将“bar”设置为
 *  ”hi",此时free = 1,表示value字符串后面有1个字节大小的空闲空间。
 *
 * <free> is always an unsigned 8 bit number, because if after an
 * update operation there are more than a few free bytes, the zipmap will be
 * reallocated to make sure it is as small as possible.
 *  free字段是一个占1个字节的整型数,它的值一般都比较小,如果空闲区间太大,zipmap会进行调整以使整个map尽可能小。
 *
 *  zipmap也存在一个结尾符,占用1个字节,其值为255。
 *  
 *  从上面的介绍可以看出:zipmap实质上是用一个字符串数组来依次保存key和value,查询时是依次遍列每个key-value对,直到查到为止。
 *
 * The most compact representation of the above two elements hash is actually:
 *
 * "\x02\x03foo\x03\x00bar\x05hello\x05\x00world\xff"
 *
 * Note that because keys and values are prefixed length "objects",
 * the lookup will take O(N) where N is the number of elements
 * in the zipmap and *not* the number of bytes needed to represent the zipmap.
 * This lowers the constant times considerably.
 */

#include <stdio.h>
#include <string.h>
#include "zmalloc.h"
#include "endianconv.h"

 /* 说明:为了描述方便,这里将key-value键值对的key称为key节点,value称为value节点 */

#define ZIPMAP_BIGLEN 254   // zipmap的元素个数超过253时的标识符
#define ZIPMAP_END 255      // zipmap的结尾符

/* The following defines the max value for the <free> field described in the
 * comments above, that is, the max number of trailing bytes in a value. */
/* free字段的最大值,也就是value后面的最大空闲字节数 */
#define ZIPMAP_VALUE_MAX_FREE 4

/* The following macro returns the number of bytes needed to encode the length
 * for the integer value _l, that is, 1 byte for lengths < ZIPMAP_BIGLEN and
 * 5 bytes for all the other lengths. */
/* 工具宏,用来确定len字段所占用的字节数。简单地测试第一个字节的值与254的大小关系 */
#define ZIPMAP_LEN_BYTES(_l) (((_l) < ZIPMAP_BIGLEN) ? 1 : sizeof(unsigned int)+1)

/* Create a new empty zipmap. */
/* 创建一个空的zipmap结构 */
unsigned char *zipmapNew(void) {
    // 初始化时只有2个字节,第1个字节表示zipmap保存的key-value对的个数,第2个字节为结尾符
    unsigned char *zm = zmalloc(2);

    // 当前保存的键值对个数为0
    zm[0] = 0; /* Length */
    zm[1] = ZIPMAP_END;
    return zm;
}

/* Decode the encoded length pointed by 'p' */
/* 获取len字段的数值(即随后字符串的长度),其原理很简单:查看第一个字节的数值,如果该数值小于254,则直接返回,
    否则读取接下来的4个字节内容表示的数值。 */
static unsigned int zipmapDecodeLength(unsigned char *p) {
    unsigned int len = *p;

    if (len < ZIPMAP_BIGLEN) return len;
    // 读取随后4个字节的内容
    memcpy(&len,p+1,sizeof(unsigned int));
    // 统一转化为小端模式表示
    memrev32ifbe(&len);
    return len;
}

/* Encode the length 'l' writing it in 'p'. If p is NULL it just returns
 * the amount of bytes required to encode such a length. */
/* 将长度len编码到p指针指向的内存空间 */
static unsigned int zipmapEncodeLength(unsigned char *p, unsigned int len) {
    if (p == NULL) {
        return ZIPMAP_LEN_BYTES(len);
    } else {
        if (len < ZIPMAP_BIGLEN) {
            // 长度小于254,只需要1个字节表示
            p[0] = len;
            return 1;
        } else {
            // 长度大于等于254,第一个字节赋值为254,接下来的4歌字节才是真正的长度值
            p[0] = ZIPMAP_BIGLEN;
            memcpy(p+1,&len,sizeof(len));
            memrev32ifbe(p+1);
            return 1+sizeof(len);
        }
    }
}

/* Search for a matching key, returning a pointer to the entry inside the
 * zipmap. Returns NULL if the key is not found.
 *
 * If NULL is returned, and totlen is not NULL, it is set to the entire
 * size of the zimap, so that the calling function will be able to
 * reallocate the original zipmap to make room for more entries. */
 /* 按关键字key查找zipmap,如果totlen不为NULL,函数返回后存放zipmap占用的字节数 */
static unsigned char *zipmapLookupRaw(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned int *totlen) {
    // zipmap中第1个字节是zmlen字段,zm+1跳过第1个字节
    unsigned char *p = zm+1, *k = NULL;
    unsigned int l,llen;

    // 从前往后查找
    while(*p != ZIPMAP_END) {
        unsigned char free;

        /* Match or skip the key */
        // 确定key字符串的长度
        l = zipmapDecodeLength(p);
        // 确定保存key字符串长度所需要的字节数,也就是len字段所需要的字节数
        llen = zipmapEncodeLength(NULL,l);
        // 比较当前key与给定key是否匹配
        if (key != NULL && k == NULL && l == klen && !memcmp(p+llen,key,l)) {
            /* Only return when the user doesn't care
             * for the total length of the zipmap. */
            // 如果totlen为NULL,表示函数调用者不关心zipmap占用的字节数,此时直接返回p,否则先记录下p指针然后继续遍历
            if (totlen != NULL) {
                k = p;
            } else {
                return p;
            }
        }
        // p加上llen和l,到了value节点处
        p += llen+l;
        /* Skip the value as well */
        // 确定value字符串的长度
        l = zipmapDecodeLength(p);
        // 确定保存value字符串长度所需要的字节数,也就是len字段所需要的字节数
        p += zipmapEncodeLength(NULL,l);
        // 读出free字段的值(前面我们讲过:free只占用一个字节)
        free = p[0];
        // 跳到下一个key节点的
        p += l+1+free; /* +1 to skip the free byte */
    }
    // 到这里遍历完整个zipmap,得到其占用的字节数
    if (totlen != NULL) *totlen = (unsigned int)(p-zm)+1;
    return k;
}

/* 存储由长度为klen的key和长度为vlen的value组成的键值对所需要的字节数*/
static unsigned long zipmapRequiredLength(unsigned int klen, unsigned int vlen) {
    unsigned int l;

    l = klen+vlen+3;
    if (klen >= ZIPMAP_BIGLEN) l += 4;
    if (vlen >= ZIPMAP_BIGLEN) l += 4;
    return l;
}

/* Return the total amount used by a key (encoded length + payload) */
/* 获取key节点占用的字节数,即len字段 + key字符串长度 */
static unsigned int zipmapRawKeyLength(unsigned char *p) {
    // 获取key字符串的长度
    unsigned int l = zipmapDecodeLength(p);
    // 加上保存key字符串长度所需要的字节数
    return zipmapEncodeLength(NULL,l) + l;
}

/* Return the total amount used by a value
 * (encoded length + single byte free count + payload) */
/* 获取value节点占用的字节数,即len字段 + 1个字节free字段 + value字符串长度 + 空闲空间大小 */
static unsigned int zipmapRawValueLength(unsigned char *p) {
    // 获取value字符串的长度
    unsigned int l = zipmapDecodeLength(p);
    unsigned int used;

    // 获取保存value字符串长度所需要的字节数
    used = zipmapEncodeLength(NULL,l);
    // p[used]里面存储着空闲空间的大小
    used += p[used] + 1 + l;
    return used;
}

/* If 'p' points to a key, this function returns the total amount of
 * bytes used to store this entry (entry = key + associated value + trailing
 * free space if any). */
/* 如果p指向key节点,则该函数返回存储整个键值对所占用的字节数,包括key节点长度 + value节点长度 + 空闲字节数(如果有) */
static unsigned int zipmapRawEntryLength(unsigned char *p) {
    unsigned int l = zipmapRawKeyLength(p);
    return l + zipmapRawValueLength(p+l);
}

/* 重新调整zipmap的大小 */
static inline unsigned char *zipmapResize(unsigned char *zm, unsigned int len) {
    // 重新分配空间,注意是realloc,即在原空间重新分配
    zm = zrealloc(zm, len);
    // 设置结尾符
    zm[len-1] = ZIPMAP_END;
    return zm;
}

/* Set key to value, creating the key if it does not already exist.
 * If 'update' is not NULL, *update is set to 1 if the key was
 * already preset, otherwise to 0. */
/* 根据key设置value,如果key不存在则创建相应的键值对,参数update用来辨别更新操作和添加操作。 */
unsigned char *zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char *val, unsigned int vlen, int *update) {
    unsigned int zmlen, offset;
    // 计算存储key和value所需要的字节数
    unsigned int freelen, reqlen = zipmapRequiredLength(klen,vlen);
    unsigned int empty, vempty;
    unsigned char *p;

    /************************************************************************
     *  下面这段代码用于在zipmap留出足够的空间来容纳新插入的键值对或新的value值,尚未写入
     ************************************************************************/
    freelen = reqlen;
    if (update) *update = 0;
    // 在zipmap中查找key,函数返回后zmlen中保存了zipmap所占用的字节数。
    p = zipmapLookupRaw(zm,key,klen,&zmlen);
    if (p == NULL) {
        /* Key not found: enlarge */
        // 如果key指定的键值对不存在,则对zipmap扩容,为容纳新的键值对准备内存空间
        // zipmapResize执行的是realloc操作
        zm = zipmapResize(zm, zmlen+reqlen);
        // 此时p指向扩容前zipmap的结尾符,将从这里添加新的键值对
        p = zm+zmlen-1;
        // 更新zipmap所占用的内存空间大小
        zmlen = zmlen+reqlen;

        /* Increase zipmap length (this is an insert) */
        // 更新zipmap中保存的键值对数量,即zmlen字段
        if (zm[0] < ZIPMAP_BIGLEN) zm[0]++;

    } else {
        /* Key found. Is there enough space for the new value? */
        /* 找到可对应的键值对,执行更新操作。这里需要考虑value节点的空间大小是否能够容纳新值 */
        /* Compute the total length: */
        if (update) *update = 1;
        // 求出旧value节点的空间大小
        freelen = zipmapRawEntryLength(p);
        if (freelen < reqlen) {
            /* Store the offset of this key within the current zipmap, so
             * it can be resized. Then, move the tail backwards so this
             * pair fits at the current position. */
             // 旧节点的空间太小,需要扩容操作,zipmapResize函数会重新分配空间,所以需要记录p指针的偏移量
            offset = p-zm;
            zm = zipmapResize(zm, zmlen-freelen+reqlen);
            p = zm+offset;

            /* The +1 in the number of bytes to be moved is caused by the
             * end-of-zipmap byte. Note: the *original* zmlen is used. */
            // 移动旧value节点以后的元素以确保有足够的空间容纳新值( +1是将尾部结尾符一起移动)
            memmove(p+reqlen, p+freelen, zmlen-(offset+freelen+1));
            zmlen = zmlen-freelen+reqlen;
            freelen = reqlen;
        }
    }

    /* We now have a suitable block where the key/value entry can
     * be written. If there is too much free space, move the tail
     * of the zipmap a few bytes to the front and shrink the zipmap,
     * as we want zipmaps to be very space efficient. */
    // freelen表示经上步骤后流出来的空余空间大小,reqlen表示插入或更新键值对所需要的空间,两者的差就是free字段的
    // 的值,如果该值过大zipmap会自动调整。下面这段代码就是完成调整功能。
    empty = freelen-reqlen;
    if (empty >= ZIPMAP_VALUE_MAX_FREE) {
        /* First, move the tail <empty> bytes to the front, then resize
         * the zipmap to be <empty> bytes smaller. */
        offset = p-zm;
        memmove(p+reqlen, p+freelen, zmlen-(offset+freelen+1));
        zmlen -= empty;
        zm = zipmapResize(zm, zmlen);
        p = zm+offset;
        vempty = 0;
    } else {
        vempty = empty;
    }

    /******************************************
     *  下面的操作是讲key和value写入zipmap指定位置
     *******************************************/
    /* Just write the key + value and we are done. */
    /* Key: */
    // 对key的长度编码并写入zipmap中
    p += zipmapEncodeLength(p,klen);
    // 写入key字符串
    memcpy(p,key,klen);
    // 移动指针到value写入位置
    p += klen;
    /* Value: */
    // 对value的长度编码并写入zipmap中
    p += zipmapEncodeLength(p,vlen);
    // 写入free字段
    *p++ = vempty;
    // 写入value
    memcpy(p,val,vlen);
    return zm;
}

/* Remove the specified key. If 'deleted' is not NULL the pointed integer is
 * set to 0 if the key was not found, to 1 if it was found and deleted. */
/* 根据key删除指定的键值对 */
unsigned char *zipmapDel(unsigned char *zm, unsigned char *key, unsigned int klen, int *deleted) {
    unsigned int zmlen, freelen;
    // 看判断该键值对是否在zipmap中,如果不存在则直接返回
    unsigned char *p = zipmapLookupRaw(zm,key,klen,&zmlen);
    if (p) {
        // 下面三句代码执行删除操作,其实就是内存块的移动操作
        freelen = zipmapRawEntryLength(p);
        memmove(p, p+freelen, zmlen-((p-zm)+freelen+1));
        zm = zipmapResize(zm, zmlen-freelen);

        /* Decrease zipmap length */
        if (zm[0] < ZIPMAP_BIGLEN) zm[0]--;

        if (deleted) *deleted = 1;
    } else {
        if (deleted) *deleted = 0;
    }
    return zm;
}

/* Call before iterating through elements via zipmapNext() */
/* zipmapNext迭代器函数,还记得前面我们分析过zipmap第一个字节是zmlen字段吗?下面这个函数就是跳过第一个字节返回
    指向第一个键值对的首地址 */
unsigned char *zipmapRewind(unsigned char *zm) {
    return zm+1;
}

/* This function is used to iterate through all the zipmap elements.
 * In the first call the first argument is the pointer to the zipmap + 1.
 * In the next calls what zipmapNext returns is used as first argument.
 * Example:
 *
 * unsigned char *i = zipmapRewind(my_zipmap);
 * while((i = zipmapNext(i,&key,&klen,&value,&vlen)) != NULL) {
 *     printf("%d bytes key at $p\n", klen, key);
 *     printf("%d bytes value at $p\n", vlen, value);
 * }
 */
/* zipmap的迭代器式遍历函数,典型用法如下:
 *
 *      unsigned char *i = zipmapRewind(my_zipmap);
 *      while((i = zipmapNext(i,&key,&klen,&value,&vlen)) != NULL) {
 *          printf("%d bytes key at $p\n", klen, key);
 *          printf("%d bytes value at $p\n", vlen, value);
 *      }
 */
unsigned char *zipmapNext(unsigned char *zm, unsigned char **key, unsigned int *klen, unsigned char **value, unsigned int *vlen) {
    // 如果达到尾部,直接返回NULL
    if (zm[0] == ZIPMAP_END) return NULL;
    // 获取key
    if (key) {
        *key = zm;
        *klen = zipmapDecodeLength(zm);
        *key += ZIPMAP_LEN_BYTES(*klen);
    }
    zm += zipmapRawKeyLength(zm);
    // 获取value
    if (value) {
        // +1是为了跳过free字段,该字段占用一个字节
        *value = zm+1;
        *vlen = zipmapDecodeLength(zm);
        *value += ZIPMAP_LEN_BYTES(*vlen);
    }
    // 此时zm指向下一个键值对的首地址
    zm += zipmapRawValueLength(zm);
    return zm;
}

/* Search a key and retrieve the pointer and len of the associated value.
 * If the key is found the function returns 1, otherwise 0. */
/* 根据key值查找相应的value值,其实是对zipmapLookupRaw的包装 */
int zipmapGet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char **value, unsigned int *vlen) {
    unsigned char *p;

    if ((p = zipmapLookupRaw(zm,key,klen,NULL)) == NULL) return 0;
    p += zipmapRawKeyLength(p);
    *vlen = zipmapDecodeLength(p);
    *value = p + ZIPMAP_LEN_BYTES(*vlen) + 1;
    return 1;
}

/* Return 1 if the key exists, otherwise 0 is returned. */
/* 判断某个key是否存在 */
int zipmapExists(unsigned char *zm, unsigned char *key, unsigned int klen) {
    return zipmapLookupRaw(zm,key,klen,NULL) != NULL;
}

/* Return the number of entries inside a zipmap */
/* 返回zipmap中键值对的个数,如果zmlen字段的值小于254,值zmlen的值就是所要求得返回值,否则需要遍历整个zipmap */
unsigned int zipmapLen(unsigned char *zm) {
    unsigned int len = 0;
    if (zm[0] < ZIPMAP_BIGLEN) {
        len = zm[0];
    } else {
        unsigned char *p = zipmapRewind(zm);
        while((p = zipmapNext(p,NULL,NULL,NULL,NULL)) != NULL) len++;

        /* Re-store length if small enough */
        if (len < ZIPMAP_BIGLEN) zm[0] = len;
    }
    return len;
}

/* Return the raw size in bytes of a zipmap, so that we can serialize
 * the zipmap on disk (or everywhere is needed) just writing the returned
 * amount of bytes of the C array starting at the zipmap pointer. */
/* 获取整个zipmap占用的字节数,其实是对zipmapLookupRaw的包装 */
size_t zipmapBlobLen(unsigned char *zm) {
    unsigned int totlen;
    zipmapLookupRaw(zm,NULL,0,&totlen);
    return totlen;
}

#ifdef ZIPMAP_TEST_MAIN
/* 格式化输出函数 */
void zipmapRepr(unsigned char *p) {
    unsigned int l;

    printf("{status %u}",*p++);
    while(1) {
        if (p[0] == ZIPMAP_END) {
            printf("{end}");
            break;
        } else {
            unsigned char e;

            l = zipmapDecodeLength(p);
            printf("{key %u}",l);
            p += zipmapEncodeLength(NULL,l);
            if (l != 0 && fwrite(p,l,1,stdout) == 0) perror("fwrite");
            p += l;

            l = zipmapDecodeLength(p);
            printf("{value %u}",l);
            p += zipmapEncodeLength(NULL,l);
            e = *p++;
            if (l != 0 && fwrite(p,l,1,stdout) == 0) perror("fwrite");
            p += l+e;
            if (e) {
                printf("[");
                while(e--) printf(".");
                printf("]");
            }
        }
    }
    printf("\n");
}

/* 下面是一些测试代码 */
int main(void) {
    unsigned char *zm;

    zm = zipmapNew();

    zm = zipmapSet(zm,(unsigned char*) "name",4, (unsigned char*) "foo",3,NULL);
    zm = zipmapSet(zm,(unsigned char*) "surname",7, (unsigned char*) "foo",3,NULL);
    zm = zipmapSet(zm,(unsigned char*) "age",3, (unsigned char*) "foo",3,NULL);
    zipmapRepr(zm);

    zm = zipmapSet(zm,(unsigned char*) "hello",5, (unsigned char*) "world!",6,NULL);
    zm = zipmapSet(zm,(unsigned char*) "foo",3, (unsigned char*) "bar",3,NULL);
    zm = zipmapSet(zm,(unsigned char*) "foo",3, (unsigned char*) "!",1,NULL);
    zipmapRepr(zm);
    zm = zipmapSet(zm,(unsigned char*) "foo",3, (unsigned char*) "12345",5,NULL);
    zipmapRepr(zm);
    zm = zipmapSet(zm,(unsigned char*) "new",3, (unsigned char*) "xx",2,NULL);
    zm = zipmapSet(zm,(unsigned char*) "noval",5, (unsigned char*) "",0,NULL);
    zipmapRepr(zm);
    zm = zipmapDel(zm,(unsigned char*) "new",3,NULL);
    zipmapRepr(zm);

    printf("\nLook up large key:\n");
    {
        unsigned char buf[512];
        unsigned char *value;
        unsigned int vlen, i;
        for (i = 0; i < 512; i++) buf[i] = 'a';

        zm = zipmapSet(zm,buf,512,(unsigned char*) "long",4,NULL);
        if (zipmapGet(zm,buf,512,&value,&vlen)) {
            printf("  <long key> is associated to the %d bytes value: %.*s\n",
                vlen, vlen, value);
        }
    }

    printf("\nPerform a direct lookup:\n");
    {
        unsigned char *value;
        unsigned int vlen;

        if (zipmapGet(zm,(unsigned char*) "foo",3,&value,&vlen)) {
            printf("  foo is associated to the %d bytes value: %.*s\n",
                vlen, vlen, value);
        }
    }
    printf("\nIterate through elements:\n");
    {
        unsigned char *i = zipmapRewind(zm);
        unsigned char *key, *value;
        unsigned int klen, vlen;

        while((i = zipmapNext(i,&key,&klen,&value,&vlen)) != NULL) {
            printf("  %d:%.*s => %d:%.*s\n", klen, klen, key, vlen, vlen, value);
        }
    }
    return 0;
}
#endif
[ESP32S3N16R8][LVGL8.3.0][IDF5.2.3]ST7701S RGB屏幕驱动[vscode最详细配置] ST7701S ESP32S3 基于ESP-IDF5.2.3编写,编程环境为vscode。本教程详细讲述了esp32s3配置st7701s屏幕驱动以及gt911触摸驱动并关联到lvgl8.3.0,使用IDF5.23示例rgb_panel进行测试删除了其里面的屏幕驱动,改为单独编写屏幕和触摸驱动,最后完成了实列。 阅读详情

相关推荐

无需Root!用VirtualXposed+JustTrustMe在安卓高版本上抓包拼多多API数据

本文详细介绍了在安卓高版本设备上无需Root权限即可抓取拼多多API数据的完整方案,通过VirtualXposed与JustTrustMe组合技术,有效绕过SSL Pinning限制。文章涵盖工具准备、环境配置、应用处理及实战分析,特别针对PDD应用的混合通信策略提供了专业解决方案,适合开发者进行竞品分析和数据采集。

weixin_28678151的博客 453

Redis源码解析——Zipmap

本文介绍的是Redis中Zipmap的原理和实现。(转载请指明出于breaksoftware的csdn博客) 基础结构 Zipmap是为了实现保存Pair(String,String)数据的结构,该结构包含一个头信息、一系列字符串对(之后把一个“字符串对”称为一个“元素”(ELE))和一个尾标记。用图形表示该结构就是: Redis源码中并没...

方亮的专栏 5715

网络调试和分析工具 Proxyman Premium

它支持MacOS、iOS和iPadOS等操作系统,提供丰富的功能,如查看和编辑HTTP/HTTPS请求和响应、设置断点、过滤请求等。Proxyman Premium还支持WebSocket、GraphQL和gRPC等现代网络协议调试,并通过直观且图形化的操作界面,使用户能高效地监控和操纵网络数据流。此外,它还具备多种高级功能,如WebSocket支持、DNS欺骗、自定义SSL证书等,为开发人员提供极大的便利。同时,由于软件版本更新和界面调整,上述步骤可能会有所变化,建议您参考最新版本的官方文档进行操作。

llhf688的博客 2000

redis之zipmap

一、简介 zipmap是一个为优化Map<String, String>数据结构大小的结构。 查找时间复杂度O(n) 内存结构紧凑,访问效率高 适用少量元素的hash, 一旦元素个数达到一定限制就会转化为正在的hash结构 二、结构图 三、实现 3.1 宏定义 #define ZIPMAP_BIGLEN 254 #define ZIPMAP_END 255 /* The following defines the max value for the <free> field

happytree001的博客 606

AWS 小白书 [1] 全球架构

小白应该知道的 AWS 知识

boshansolo的博客 704

Labelme AI-Polygon闪退排查与修复指南

本文详细介绍了Labelme AI-Polygon功能闪退问题的排查与修复方法,包括环境配置检查、模型路径设置、依赖库版本控制等关键步骤。通过系统性的解决方案和实用技巧,帮助用户快速解决闪退问题,确保数据标注工作的高效进行。

weixin_29311017的博客 140

【Redis源码剖析】 - Redis内置数据结构之压缩字典zipmap

原创作品,转载请标明:http://blog.youkuaiyun.com/Xiejingfa/article/details/51111230 Redis源码剖析系列文章汇总:传送门 今天为大家带来Redis中zipmap数据结构的分析,该结构定义在zipmap.h和zipmap.c文件中。我把zipmap称作“压缩字典”(不知道这样称呼正不正确)是因为zipmap利用字符串实现了一个简单的hash_t...

上善若水,人淡如菊 5916

从CentOS迁移到openEuler 24.03 LTS:手把手教你部署Java+MySQL+Redis+Nginx全栈环境

本文详细指导如何从CentOS迁移到openEuler 24.03 LTS,并部署Java+MySQL+Redis+Nginx全栈环境。涵盖系统配置、软件安装、性能优化及环境验证,助力企业级用户高效完成服务器迁移与部署。

weixin_26938645的博客 388

zipmap-压缩字典

前言 hash 数据类型是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。每个 hash 可以存储 232 - 1 键值对(40多亿)。 redis 在 hash 类型数量比较小的时候会选择使用 zipmap 来实现存储。先看看 Redis 是怎么定义 zipmap的: String -> String Map 数据结构优化了大小。 该文件实现了一个数据结构,将字符串映射到其他字符串,实现了一个 O(n) 查找数据结构,该结构设计为非

一七令 1550

最新Win10离线安装.NET Framework 3.5的方法(附离线包2022/3/22)

win10 .net framework 3.5离线安装

别告诉我有Bug 27万+

深入解析国家企业征信系统瑞数6vmp算法逆向实战

本文深入解析了国家企业征信系统所采用的双层防护机制,重点聚焦于瑞数6代vmp算法的逆向实战。文章详细拆解了从加速乐521状态码的绕过,到瑞数412挑战的核心算法还原过程,提供了通过动态Hook、日志分析进行算法逆向,并用Python实现自动化请求链的完整技术方案,旨在为安全研究与技术学习提供参考。

yellow的专栏 612

redis学习笔记(7)---压缩字典zipmap

zipmap  在hashtable实现中,Redis引入了zipmap数据结构,保证在hashtable刚创建以及元素较少时,用更少的内存来存储,同时对查询的效率也不会受太大的影响。   zipmap利用字符串实现了简单的hash表,来存储少量key-value对。内存布局    zipmap的内存布局如下:      1)zmlen:1个字节 ,记录当前zipmap中key-value对

qinm的专栏 4095

别再用Selenium被秒封了!2026最新反爬全突破+动态渲染实战(通过率99%)

做爬虫的同学,肯定都有过这样的经历:遇到一个纯前端渲染的网站,Requests抓包只能拿到一堆空的div标签,所有数据都是JS动态生成的。这时候你想到了Selenium,结果刚打开浏览器,网站直接跳出来一个"检测到机器人访问"的页面,或者直接返回403。我之前做一个跨境电商的爬虫项目,就踩了这个大坑。一开始用默认的Selenium ChromeDriver,10次请求有9次被封,通过率不到10%。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1385

Redis源码剖析之zipmap

以下涉及到的源码均为redis5.0-rc3版本的代码【点击查看官方源码】 zipmap zipmap是为优化string映射到其他string这种结构而生的,故名思意其能节约内存。 zipmap是redis中hash结构的底层涉及之一。当hash存储的元素数量少的情况下,底层使用zipmap结构,一旦元素数量达到一个限定值的时候就会自动切换成哈希表。 由于zipmap的特性,hash结...

相逢一笑 354

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

【Redis源码剖析】 - Redis内置数据结构之压缩字典zipmap

今天为大家带来Redis中zipmap数据结构的分析,该结构定义在zipmap.h和zipmap.c文件中。我把zipmap称作“压缩字典”(不知道这样称呼正不正确)是因为zipmap利用字符串实现了一个简单的hash_table结构,又通过固定的字节表示节省空间。zipmap和前面介绍的ziplist结构十分类似,我们可以对比地进行学习: redis中定义了双向链表list,但是这种链

Franco的博客 494

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

猫抓Cat-Catch 浏览器资源嗅探指南:三步保存在线视频与音频

猫抓 Cat-Catch 是一款开源的浏览器资源嗅探扩展。它把当前页面已加载的视频和音频以列表形式列出来,支持捕获 MP4、MP3、M3U8 等格式,适合想把在线媒体存到本地的用户。 ## 01 获取项目概览 - 开源免费:代码以 GPL-3.0 许可发布,行为可自行审查。 - 本地运行:嗅探到的资源与数据只在本地处理,不上传远程服务器。 - 平台覆盖:Chrome、Edge、Firefox(

gitblog_00090的博客 394

Redis源码剖析--源码结构解析

找工作那会儿,看了黄建宏老师的《Redis设计与实现》,对redis的部分实现有了一个简明的认识。在面试过程中,redis确实成为了面试官考核我的一个亮点,恰好以后的工作又与redis有着千丝万缕的联系,于是就想趁着毕业前的这段时间把redis的源代码研究一下,为以后的工作打个良好的基础。 Redis简介redis全称REmote DIctionary Server,是一个由Salvatore S

ZeeCoder 1942

redis源码之main()函数剖析

今天看了redis的源码之中的main()函数,将大概的流程进行梳理。 在代码中进行了相应的注释,便于阅读者理解: int main(int argc, char **argv) { struct timeval tv; int j; #ifdef REDIS_TEST if (argc == 3 && !strcasecmp(argv[1], "t...

一缕阳光的博客 1155

Redis源码剖析--对象object

前面一系列的博客分析了Redis的基本数据结构,有动态字符串sds、双端链表sdlist、字典dict、跳跃表skiplist、整数集合intset和压缩列表ziplist等,这些数据结构对于用户来说是不可见的。Redis在这些数据结构的基础上构建了对用户可见的五种类型,分别是string、hash、list、set和zset,为了更方便的使用这五种数据类型,Redis定义了RedisObject结

ZeeCoder 2860

【Redis】String源码剖析:512MB大字符串的内存管理之道

Redis中,String类型底层实现的数据结构为int和SDS(简单动态字符串)。为什么不使用C原生的字符数组,而要使用SDS呢?获取字符串长度C字符串获取长度会使用strlen函数,时间复杂度为O(n),其获取字符串长度的逻辑如下图所示:而SDS直接用一个类项记录了字符串长度,获取长度的时间复杂度为O(1)。字符串溢出C字符串对于越界检查不是很完善,会出现内存踩踏的问题:而SDS会记录可用空间,在需要扩容时自动扩容。存储特殊类型数据。

白晨的博客 1170

Valkey数据结构实战:Zipmap与Listpack内存优化方案

你是否曾遇见过Redis/Valkey实例在存储百万级小键值对时内存占用异常飙升?当普通哈希表(Hash Table)因元数据开销吞噬30%以上内存时,Valkey的两种紧凑型数据结构——Zipmap与Listpack正成为解决这类问题的关键技术。本文将深入剖析这两种序列化格式的底层实现,通过性能对比、适用场景分析和实战配置指南,帮助开发者掌握Valkey内存优化的核心方法论。 读完本文你将获得...

gitblog_01052的博客 486

唯快不破:redis源码剖析01-学习计划

最近发现阅读源码是增长编程能力的九阳真经,再加上最近在学习UNP第一册,所以决定阅读分析一下现有开源项目redis。项目计划一天一个模块,逐步完成对redis源码的学习。 Redis的代码规模比较小,非常适合学习,是一份非常不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的。希望最终能把他啃完吧,C语言好久不用,快忘光了。分析源码的第一步,先别急着想着从哪开始看起,先浏览一下源...

昭君出塞的专栏 285

Redis 源码分析

Redis 源码分析

zjw12221的专栏 1100

Redis源码剖析之robj(redisObject)

我们在之前的文章中已经了解过一部分Redis的数据结构了,尤其是dict 中讲到,可以把redis看做一个hashtable,存储了一堆的key-value,今天就来看下key-value中value的主要存储结构redisObject(后文统称robj)。 robj的详细代码见object.c 字段详解 相对与其他几个数据结构,robj相对简单,因为只包含了几个字段,含义都很明确。 typedef struct redisObject { unsigned type:4; // 数据

xindoo 1483

【Redis源码剖析】 - Redis数据类型之redisObject

原创作品,转载请标明:http://blog.youkuaiyun.com/Xiejingfa/article/details/51140041 Redis源码剖析系列文章汇总:传送门 在前面六篇文章中,我们逐一分析了字符串sds、双向链表list、字典dict、压缩列表ziplist、压缩字典zipmap、整数集合inset这几种Redis内置数据结构的源码实现(实际上还有一种称作skiplist的结构...

上善若水,人淡如菊 8685

Redis源码剖析:对象介绍

Redis使用对象来表示数据库中的键和值,每次当我们在Redis数据库中新创建一个键值对时,我们会至少会创建两个对象,一个是键对象,另一个是值对象。 typedef struct redisObject { // 类型 unsigned type:4; // 编码 unsigned encoding:4; // 对象最后一次被访问的时间 unsigned lru:REDIS_LRU_BITS; /* lru time (relative to ser

wwxy1995的博客 176
上一篇: 服务端数据推送方式-SSE(server-sent event)简介及上手小demo
下一篇: Mysql创建用户后,执行grant授权命令,提示can't find any matching row in the user table
L瑜
博客等级 码龄14年 133粉丝 39原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值