办理咨询公司需要什么条件/黄山网站seo

办理咨询公司需要什么条件,黄山网站seo,用易语言做钓鱼网站,机械网站模板Redis数据结构 1、RedisObject对象2、简单动态字符串2.1 SDS定义2.2 SDS与C语言的区别2.3 SDS的空间分配策略2.3.1 空间预分配2.3.2 惰性空间释放 2.4 SDS的API 3、链表3.1 链表的定义3.2 链表的API 4、字典4.1 字典的定义4.2 哈希算法4.3 哈希表的扩缩4.3.1 哈希表扩缩的判断依…

Redis数据结构

  • 1、RedisObject对象
  • 2、简单动态字符串
    • 2.1 SDS定义
    • 2.2 SDS与C语言的区别
    • 2.3 SDS的空间分配策略
      • 2.3.1 空间预分配
      • 2.3.2 惰性空间释放
    • 2.4 SDS的API
  • 3、链表
    • 3.1 链表的定义
    • 3.2 链表的API
  • 4、字典
    • 4.1 字典的定义
    • 4.2 哈希算法
    • 4.3 哈希表的扩缩
      • 4.3.1 哈希表扩缩的判断依据
      • 4.3.2 哈希表rehash
      • 4.3.2 渐进式rehash
    • 4.4 字典的API
  • 5、跳跃表
    • 5.1 跳跃表的实现
    • 5.2 跳跃表的API
  • 6、整数集合
    • 6.1 整数集合定义
    • 6.2 整数集合API
  • 7、压缩列表
    • 7.1 压缩列表定义
    • 7.2 压缩列表的API

如有侵权,请联系~
如有错误,也欢迎批评指正~
本篇文章大部分是来自学习《Redis设计与实现》的笔记

1、RedisObject对象

在介绍Redis各种类型的数据结构之前,先了解一下RedisObject数据结构。RedisObject翻译过来就是redis对象,它在redis服务端无时无刻都在,可以说redis服务端数据都是以redisObject形式存在。
例如,存储hash值,那么键是存储相应字符串的redisObject对象,值是存储相应hash表的redisObject对象。

先整体看下redis数据协议转换【redis之后在交互的时候是resp协议,在redis服务端都是redisObject】:
在这里插入图片描述
RedisObject的数据结构:

typedef struct redisObject {unsigned type:4;       // 数据类型(如字符串、列表、哈希等)unsigned encoding:4;   // 编码方式(如 raw、int、ziplist 等)unsigned lru:LRU_BITS; // LRU 时间戳或 LFU 数据(用于淘汰策略)int refcount;          // 引用计数(用于内存管理)void *ptr;             // 指向实际数据的指针
} robj;

字段说明:

  • type:表示数据的类型,例如字符串(REDIS_STRING)、列表(REDIS_LIST)、哈希(REDIS_HASH)等。
  • encoding:表示数据的编码方式,例如原始字符串(REDIS_ENCODING_RAW)、整数(REDIS_ENCODING_INT)、压缩列表(REDIS_ENCODING_ZIPLIST)等。
  • lru:用于记录对象的访问时间,支持 LRU 或 LFU 淘汰策略。
  • refcount:引用计数,用于内存管理和共享对象。当这个属性为0的时候,这个对象就会被回收。相同对象可以共享,减少内存使用。redis在初始化的时候会创建一万个字符串,值为0~9999的对象用于共享,类似于java的Integer包装类。
  • ptr:指向实际数据的指针,数据的具体存储方式由 encoding 决定。

一个存储字符串的redisObject示意图:
在这里插入图片描述
具体编码方式有哪些以及存储结构可以参考:redis对象

redis中所有的键都是字符串对象,而值可以是下面的五种类型中的任意一个。所以说的列表键其实是指的值是列表类型。同样,命令TYPE返回的也是值的类型。

所有的编码方式encoding【可以通过OBJECT ENCODING可以查看键值对的值的编码方式】:
在这里插入图片描述

数据类型编码方式底层实现切换条件
StringRAW简单动态字符串(SDS)当字符串的长度大于32字节,始终使用 SDS。
EMBSTR简单动态字符串(SDS)当字符串的长度小于等于32字节,使用 embstr 编码。
INT整数当存储的值可以表示为 64 位有符号整数时,使用 INT 编码。
ListZIPLIST压缩列表(Ziplist)元素数量 < list-max-ziplist-entries(默认 512)且每个元素大小 < list-max-ziplist-value(默认 64 字节)。
LINKEDLIST双向链表不满足上述条件时,切换为双向链表。
HashZIPLIST压缩列表(Ziplist)字段数量 < hash-max-ziplist-entries(默认 512)且每个字段大小 < hash-max-ziplist-value(默认 64 字节)。
HASHTABLE哈希表不满足上述条件时,切换为哈希表。
SetINTSET整数集合(IntSet)元素数量 < set-max-intset-entries(默认 512)且所有元素为整数时,使用 IntSet。
HASHTABLE哈希表不满足上述条件时,切换为哈希表。
ZSetZIPLIST压缩列表(Ziplist)元素数量 < zset-max-ziplist-entries(默认 128)且每个成员大小 < zset-max-ziplist-value(默认 64 字节)。
SKIPLIST跳跃表 + 字典不满足上述条件时,切换为跳跃表和字典。

接下来直接讲各种数据结构,即Redis的ptr指针指向的那一部分。

2、简单动态字符串

2.1 SDS定义

redis中的字符串都是简单动态字符串(SDS)的形式存在的。不止是set key value的键和字符串值value,连其他数据结构中存储的字符串也是以SDS形式存储,如rpush fruits "apple " “banana”,队列保存的中的"apple " "banana"两个字符串元素也是SDS存储。

SDS除了保存字符串以外,还用在缓冲区:AOF缓冲区、客户端输入输出缓冲区。

SDS的数据结构:

struct sdshdr {int len;       // 字符串的长度(已使用的字节数)int free;      // 未使用的字节数(空闲空间)char buf[];    // 实际存储字符串内容的字符数组
};

字段说明:

  • len:表示字符串的实际长度(不包括终止符 \0)。允许 O(1) 时间复杂度获取字符串长度。
  • free:表示缓冲区中未使用的字节数。用于优化内存分配和减少频繁的内存重新分配。
  • buf:存储实际的字符串内容,以 \0 结尾(兼容 C 字符串)。

针对于hello字符串对应的SDS:
在这里插入图片描述

len:等于5,因为hello字符串占用了5个字节,\0空字符并不单独算一个字符,即len、free都不会将其算入在内,\0是SDS函数自动添加的,对用户是透明的,为了能够使用c语言的一些函数。
free:等于3,表示buf申请的缓冲区中还有3个字节未使用。

2.2 SDS与C语言的区别

  1. 获取字符串的长度时间复杂度
    因为SDS存储了字符串的长度,所以时间复杂度为O(1);而C语言只存储了字符串本身,所以需要变量整个字符串才知道字符串的长度,时间复杂度为O(N)

  2. 杜绝缓冲区溢出
    例如,字符串拼接函数strcat(s1, s2)将字符串s2拼接到s1后面,但是在拼接的时候没有检测s1的内存,s1没有足够的内存【s1后面存在其他有用的数据s3】,那么拼接就会导致s2会把s3的部分数据给修改了。而SDS空间分配策略就杜绝了这个问题出现。SDS API首先会检测空间是否满足,如果不满足自动的空间扩展。

  3. 减少修改字符串导致的重分配次数
    C语言针对于一个长度为N的字符串底层是一个N+1的字符数组,每次修改字符串都需要进行内存重分配。增加字符需要扩展内存【否则,出现内存溢出问题】,减少字符需要释放内存【否则,出现内存泄漏问题】。执行内存重分配就需要系统调用,比较耗时,而Redis作为存储设备,字符串变更更是家常便饭。
    Redis通过未使用空间解耦了字符串长度和底层数据之间的绑定关系,通过未使用空间就实现了空间预分配和惰性空间释放

  4. 二进制安全
    C语言字符串必须符合某种编码,而且除了字符串的末尾为空字符之外,其他地方不允许有空字符,否则字符串就会提前结束。这就导致C语言只能存储文本数据,不能保存像图片、视频、音频等二进制数据。而SDS是根据len属性的长度确定结束,所以没有上述问题。

  5. 兼容部分C语言函数
    虽然SDS是二进制安全的,但是在内存分配和数据存储的时候都会自动的多分配一个字节用来存储空字符,这就是为了能直接兼容C语言的部分<string.h>库上的字符串函数。当然针对于中间有空字符的就不能使用C语言函数。

2.3 SDS的空间分配策略

2.3.1 空间预分配

字符串在增加的时候,SDS API就检测未使用空间是否满足,即free >= 待插入字符串的长度,如果满足,则直接插入;否则出现如下空间预分配策略:

  • 如果对字符串修改之后1 ,SDS的长度【len属性】小于1M,则程序也会分配同样大小的未使用内存;
  • 如果对字符串修改之后 ,SDS的长度【len属性】大于1M,则程序针对未使用的内存也只会申请1M的大小

上述分配策略衡量了占用内存和性能,因为字符串都大于1M,再申请一倍的未使用内存,就会占用太多内存。

2.3.2 惰性空间释放

针对于字符串缩减的时候,SDS并不会将缩减完空余的空间立刻释放掉,而是会增加free属性,防止为了以后再增加还需要进行内存重分配。当然,SDS 也提供了能够释放未使用空间的API,不会出现内存泄漏问题。

2.4 SDS的API

以下是常见的 SDS API 函数及其功能说明。【插入:一下内容通过AI生成。本人先去百度搜索没有找到相关内容,并且找到的也不太方便截图。于是直接使用AI生成,按照符合的语法生成直接负责即可。点赞AI】
1. 创建与销毁

函数名称功能描述示例代码
sdsnew创建一个新的 SDS 字符串,并初始化为指定的 C 字符串。sds s = sdsnew("hello");
sdsempty创建一个空的 SDS 字符串。sds s = sdsempty();
sdsfree释放一个 SDS 字符串,回收其占用的内存。sdsfree(s);
sdsdup复制一个 SDS 字符串,返回一个新的 SDS 实例。sds copy = sdsdup(s);

2. 修改与扩展

函数名称功能描述示例代码
sdscat将一个 C 字符串追加到 SDS 字符串的末尾。s = sdscat(s, " world"); // 结果为 "hello world"
sdscatsds将另一个 SDS 字符串追加到当前 SDS 字符串的末尾。s = sdscatsds(s1, s2);
sdscpy将一个 C 字符串复制到 SDS 字符串中,覆盖原有内容。s = sdscpy(s, "new string");
sdsgrowzero扩展 SDS 字符串的长度到指定值,并用 \0 填充新增部分。sdsgrowzero(s, 20);
sdsMakeRoomFor为 SDS 字符串分配额外的空间,确保有足够的缓冲区。s = sdsMakeRoomFor(s, 100);
sdsRemoveFreeSpace移除 SDS 字符串的空闲空间,使其占用的内存最小化。s = sdsRemoveFreeSpace(s);
sdsIncrLen调整 SDS 字符串的长度,增加或减少已使用的字节数。sdsIncrLen(s, 5); // 长度增加 5
sdsupdatelen更新 SDS 字符串的长度字段,通常在手动修改 buf 后调用。sdsupdatelen(s);

3. 查询与统计

函数名称功能描述示例代码
sdslen返回 SDS 字符串的长度(已使用的字节数)。size_t len = sdslen(s);
sdsavail返回 SDS 字符串的空闲空间大小(未使用的字节数)。size_t avail = sdsavail(s);
sdscmp比较两个 SDS 字符串,类似于 C 的 strcmp 函数。int cmp = sdscmp(s1, s2);

4. 截取与分割

函数名称功能描述示例代码
sdsrange截取 SDS 字符串的一部分,范围为 [start, end]sdsrange(s, 0, 4); // 截取前 5 个字符
sdssplitlen根据指定的分隔符将 SDS 字符串拆分为多个子字符串,返回一个数组。sds* tokens = sdssplitlen(s, sdslen(s), " ", 1, &count);
sdsjoinsds将多个 SDS 字符串连接成一个新的 SDS 字符串,使用指定的分隔符。sds joined = sdsjoinsds(tokens, count, ", ", 2);

5. 其他操作

函数名称功能描述示例代码
sdsclear清空 SDS 字符串的内容,将其长度设置为 0,但保留缓冲区。sdsclear(s);
sdsmapchars替换 SDS 字符串中的某些字符为其他字符。sdsmapchars(s, "aeiou", "AEIOU", 5); // 将元音替换为大写

3、链表

由于C语言没有链表数据结构,所以Redis进行自己构建了链表。链表在Redis中的使用还是很广的,例如列表的底层就是链表【排出压缩列表的情况】、Redis服务端存储的客户端状态、客户端的输出缓冲区、发布订阅等都用到了链表。

3.1 链表的定义

链表是由一个个链表节点串联组合而成的双向链表,先看下链表节点的数据结构:

typedef struct listNode {struct listNode *prev;  // 指向前一个节点,如果当前节点是链表的头节点,则 prev 为 NULL。struct listNode *next;  // 指向下一个节点,如果当前节点是链表的尾节点,则 next 为 NULL。void *value;            // 数据域,存储节点的值
} listNode;

链表的数据结构:

typedef struct list {listNode *head;       // 指向链表的头节点listNode *tail;       // 指向链表的尾节点unsigned long len;    // 链表的长度(节点数量)void *(*dup)(void *ptr);   // 节点值复制函数void (*free)(void *ptr);   // 节点值释放函数int (*match)(void *ptr, void *key); // 节点值匹配函数
} list;

针对于列表中的三个函数指针进行详细介绍,可以自定义下面的函数:

  • dup:用于复制链表节点的值。
    当需要复制链表时(例如深拷贝),Redis 会调用 dup 函数来复制每个节点的值。
    默认行为:如果未设置 dup 函数,则 Redis 不会对节点值进行复制,直接将指针赋值给新节点
  • free:用于释放链表节点值占用的内存。
    当删除链表节点或释放整个链表时,Redis 会调用 free 函数来释放节点值。
    默认行为:如果未设置 free 函数,则 Redis 不会释放节点值,可能导致内存泄漏。
  • 用于比较两个节点值是否相等。
    当需要查找链表中的某个节点时,Redis 会调用 match 函数来比较节点值与目标值。
    默认行为:如果未设置 match 函数,则 Redis 使用指针比较(即比较两个指针是否相同)。

redis删除策略【区别于过期策略(惰性策略和定期策略)和淘汰策略(内存不足的时候)】:

  • 如果一个key开始指向一个字符串,然后更新一个字符串,会怎么办?因为字符串底层数据结构是SDS,所以会复用前一个字符串的SDS。
  • 如果一个key开始指向一个复杂的数据结构,如列表、哈希,然后更新一个新的,会怎么办?redis会先递归删除原来的数据,然后讲新的赋值给这个key。

以链表存储字符串为例的结构图:
在这里插入图片描述

链表的特点:

  • 链表获取长度的时间复杂度为O(1)
  • 链表获取链表头和尾的时间复杂度为O(1)
  • 链表获取前一个节点和后一个节点的时间复杂度为O(1)
  • 链表不存在环的问题,因为头节点的head为null,后节点的next也为null
  • 多态:链表提供了复制dup、删除free、比较match函数,可以自定义这些函数,所以再复杂的数据结构也OK

3.2 链表的API

1. 创建与销毁

函数名称功能描述示例代码
listCreate创建一个新的空链表。list *myList = listCreate();
listRelease释放整个链表及其所有节点。listRelease(myList);

2. 添加节点

函数名称功能描述示例代码
listAddNodeHead在链表头部添加一个新节点。listAddNodeHead(myList, "value");
listAddNodeTail在链表尾部添加一个新节点。listAddNodeTail(myList, "value");
listInsertNode在指定节点之前或之后插入一个新节点。listInsertNode(myList, oldNode, "value", 1); // 1 表示在 oldNode 之后插入

3. 删除节点

函数名称功能描述示例代码
listDelNode删除链表中的指定节点。listDelNode(myList, node);

4. 查找节点

函数名称功能描述示例代码
listSearchKey在链表中查找值为指定键的节点。listNode *node = listSearchKey(myList, "key");

5. 遍历链表

函数名称功能描述示例代码
listGetIterator获取链表的迭代器,支持从头到尾或从尾到头遍历。listIter *iter = listGetIterator(myList, AL_START_HEAD);
listNext获取迭代器指向的下一个节点。listNode *node = listNext(iter);
listReleaseIterator释放链表迭代器。listReleaseIterator(iter);

6. 自定义函数

函数名称功能描述示例代码
dup自定义的节点值复制函数,用于深拷贝节点值。void *myDupFunction(void *ptr) { return strdup((char *)ptr); }
free自定义的节点值释放函数,用于释放节点值占用的内存。void myFreeFunction(void *ptr) { free(ptr); }
match自定义的节点值匹配函数,用于比较两个节点值是否相等。int myMatchFunction(void *ptr, void *key) { return strcmp((char *)ptr, (char *)key); }

4、字典

4.1 字典的定义

字典和链表一样C语言没有内置相关数据结构,都是redis自己构建实现的。字典是redis的全局哈希和哈希类型的底层实现。

哈希表的结构定义:

typedef struct dict {dictType *type;          // 类型特定函数,例如键、值等的复制、删除、对比函数或者是计算哈希值的函数void *privdata;          // 私有数据。保存类型特定函数的可选参数dictht ht[2];            // 两个哈希表(用于渐进式 rehash)。平时一直都是使用ht[0],只有在哈希表进行扩缩容的时候才会使用ht[1]long rehashidx;          // 当前 rehash 的索引(-1 表示未进行 rehash)unsigned long iterators; // 正在运行的迭代器数量
} dict;typedef struct dictht {dictEntry **table;       // 哈希表数组unsigned long size;      // 哈希表大小unsigned long sizemask;  // 掩码,用于计算索引。始终是sizemask=size-1,主要是用于计算一个键的hash索引unsigned long used;      // 已使用的、存在的键值对数量
} dictht;typedef struct dictEntry {void *key;               // 键union {void *val;uint64_t u64;int64_t s64;} v;                      // 值,这个值可以是对象或者是uint64_t整数或者是int64_t整数struct dictEntry *next;   // 指向下一个节点(解决冲突),形成链表
} dictEntry;
  • dict:哈希表的顶层结构,包含两个哈希表(ht[0] 和 ht[1]),用于支持渐进式 rehash。
  • dictht:单个哈希表的结构,包含数组、大小、掩码和已使用节点数。
  • dictEntry:哈希表中的单个节点,包含键、值和指向下一个节点的指针。

字典整体的数据结构:
在这里插入图片描述

4.2 哈希算法

插入数据的大致流程:

  • 通过字典中设置的哈希函数,计算键key的哈希值:hash = dict.type.hashFunction(key);
  • 计算该键值对需要插入的数组索引:index = hash & sizemask;
  • 如果table数组的该索引处存在键值对,则插入到链表的头部,即头插法。

redis默认使用的哈希函数是Murmurhash2算法,并且出现冲突、哈希碰撞则使用链地址法解决冲突问题。

4.3 哈希表的扩缩

虽然程序的不断运行,哈希表中的数据会实时的变化,不断的增加或者减少。为了能过依然保持哈希表的性能和空间优化,会对哈希表进行扩缩,使得负载因子【load factor = used/size】保持在合理返回。

4.3.1 哈希表扩缩的判断依据

哈希表扩展的情况:

  • 当redis正在执行bgsave【rdb持久化】或者bgwriteaof【aof重写】命令的时候,负载因子大于等于5
  • 当redis没有执行bgsave【rdb持久化】或者bgwriteaof【aof重写】命令的时候,负载因子大于等于1

哈希表收缩的情况:

  • 当负载因子小于0.1

4.3.2 哈希表rehash

  • 根据当前ht[0]中键值对数量为字典的ht[1]分配空间:

    • 当哈希表进行扩容的时候, ht[1]分配的空间大小为:第一个大于等于的ht[0].used*2的2n;
    • 当哈希表进行扩容的时候, ht[1]分配的空间大小为:第一个大于等于的ht[0].used的2n;
  • 将ht[0]上的值渐近式的rehash到ht[1]上

  • 等ht[0]上的所有键值对都迁移到ht[1]上之后,释放ht[0],将ht[1]上的哈希表赋值给ht[0],然后ht[1]创建一个新的哈希表

4.3.2 渐进式rehash

当哈希表需要扩缩容的时候,不可能等到完全将ht[0]中的数据全部迁移到ht[1]之后再对外提供服务,尤其是哈希表中存储了大量的数据。因为这会导致服务一段时间的不可用,所以使用的是渐进式的、分批次的rehash。

当创建完ht[1]哈希表之后,rehashidx就设置为0。每当redis执行增删改查的时候,就会将ht[0]哈希表对应的rehashidx索引处的所有键值对都迁移rehash到ht[1]上,然后rehashidx加一。直到ht[0]哈希表所有的键值对都rehash完成,则rehashidx=-1。

当前在进行rehash期间,redis所有的增删改查操作会在ht[0]和ht[1]两个表上进行。例如:查询是否存在某个键的时候,会首先在ht[0]上进行查找,如果ht[0]没有,还会在ht[1]哈希表上查询;插入操作则是直接插入到ht[1]上,不会在再ht[0]上进行任何的插入操作。

4.4 字典的API

1. 创建与销毁

函数名称功能描述示例代码
dictCreate创建一个新的空字典。dict *myDict = dictCreate(&type, NULL);
dictRelease释放字典及其所有节点。dictRelease(myDict);

2. 插入与更新

函数名称功能描述示例代码
dictAdd向字典中添加一个新键值对。如果键已存在,则返回错误。dictAdd(myDict, "key", "value");
dictReplace向字典中添加或更新一个键值对。如果键已存在,则更新值;否则插入新键值对。dictReplace(myDict, "key", "new_value");
dictSet设置字典中的键值对(类似于 dictReplace)。dictSet(myDict, "key", "value");

3. 删除

函数名称功能描述示例代码
dictDelete从字典中删除指定键的键值对。dictDelete(myDict, "key");
dictDeleteNoFree从字典中删除指定键的键值对,但不释放键和值的内存。dictDeleteNoFree(myDict, "key");

4. 查找

函数名称功能描述示例代码
dictFind在字典中查找指定键的节点。dictEntry *entry = dictFind(myDict, "key");
dictFetchValue获取指定键对应的值。void *value = dictFetchValue(myDict, "key");

5. 遍历

函数名称功能描述示例代码
dictGetIterator获取字典的迭代器,用于遍历所有键值对。dictIterator *iter = dictGetIterator(myDict);
dictNext获取迭代器指向的下一个节点。dictEntry *entry = dictNext(iter);
dictReleaseIterator释放字典迭代器。dictReleaseIterator(iter);

6. 其他操作

函数名称功能描述示例代码
dictExpand扩展字典的大小(通常用于优化性能)。dictExpand(myDict, new_size);
dictRehash手动触发 rehash 操作(将旧哈希表迁移到新哈希表)。dictRehash(myDict, n); // 迁移 n 个桶
dictGetHashTableSize获取字典当前哈希表的大小。unsigned long size = dictGetHashTableSize(myDict);
dictGetHashKey获取字典节点的键。void *key = dictGetHashKey(entry);
dictGetHashVal获取字典节点的值。void *value = dictGetHashVal(entry);

5、跳跃表

跳跃表是一个有序的数据结构,大部分情况下可以与平衡树相媲美,却比平衡数简单。那么为什么mysql不使用跳跃表呢?【跳跃表一般层级比较深相比于平衡树,对于磁盘读取影响比较大,而内存无关紧要】。跳跃表是有序集合的底层实现。

5.1 跳跃表的实现

跳跃表节点的数据结构:

typedef struct zskiplistNode {sds o;                      // 元素值(字符串)double score;                 // 分值,用于排序struct zskiplistNode *backward; // 指向前一个节点(仅在第一层有效)struct zskiplistLevel {struct zskiplistNode *forward; // 指向同一层的下一个节点unsigned long span;           // 到下一个节点的跨度(跨越的节点数),这样在便利得到某个元素的时候就可以通过span之后得到排名。例如o2的排名等于1+1} level[];                     // 多层索引
} zskiplistNode;

跳跃表的数据结构:

typedef struct zskiplist {struct zskiplistNode *header; // 跳跃表头节点struct zskiplistNode *tail;   // 跳跃表尾节点unsigned long length;         // 跳跃表中节点的数量int level;                    // 跳跃表的最大层数,表头的层数不算
} zskiplist;

图形化:
在这里插入图片描述

5.2 跳跃表的API

1. 创建与销毁

函数名称功能描述示例代码
zslCreate创建一个新的空跳跃表。zskiplist *zsl = zslCreate();
zslFree释放跳跃表及其所有节点。zslFree(zsl);

2. 插入

函数名称功能描述示例代码
zslInsert向跳跃表中插入一个新节点,按照分值排序。c zskiplistNode *node = zslInsert(zsl, score, ele);

3. 删除

函数名称功能描述示例代码
zslDelete从跳跃表中删除指定分值和元素值的节点。int deleted = zslDelete(zsl, score, ele, &node);
zslDeleteRangeByScore删除分值范围内的所有节点。zslDeleteRangeByScore(zsl, min, max, dict);
zslDeleteRangeByRank删除排名范围内的所有节点。zslDeleteRangeByRank(zsl, start, end, dict);

4. 查找

函数名称功能描述示例代码
zslGetElementByRank根据排名查找节点。zskiplistNode *node = zslGetElementByRank(zsl, rank);
zslIsInRange检查分值是否在跳跃表的范围内。int inRange = zslIsInRange(zsl, range);
zslFirstInRange返回分值范围内的第一个节点。zskiplistNode *node = zslFirstInRange(zsl, range);
zslLastInRange返回分值范围内的最后一个节点。zskiplistNode *node = zslLastInRange(zsl, range);

5. 遍历

函数名称功能描述示例代码
zslGetRank获取指定分值和元素值的节点的排名(从 1 开始)。unsigned long rank = zslGetRank(zsl, score, ele);

6. 辅助函数

函数名称功能描述示例代码
zslRandomLevel随机生成节点的层数。int level = zslRandomLevel();

6、整数集合

整数集合是集合的底层实现之一。当集合中元素都为整数并且数量不多的时候就会使用整数集合。

6.1 整数集合定义

数据结构定义:

typedef struct intset {uint32_t encoding;  // 编码方式,决定存储的整数类型uint32_t length;    // 集合中元素的数量int8_t contents[]; // 动态数组,用于存储整数
} intset;
  • encoding 字段:表示当前整数集合使用的编码方式,决定了每个元素占用的字节数:
    • INTSET_ENC_INT16:每个元素占用 2 字节(int16_t)。
    • INTSET_ENC_INT32:每个元素占用 4 字节(int32_t)。
    • INTSET_ENC_INT64:每个元素占用 8 字节(int64_t)。
      当插入一个超出当前编码范围的整数时,Redis 会升级编码方式(例如从 INTSET_ENC_INT16 升级到 INTSET_ENC_INT32),并重新分配内存。
  • length 字段:表示当前集合中元素的数量。
  • contents 数组:存储集合中的所有整数,按从小到大的顺序排列。
    由于整数集合是有序的,查找操作可以通过二分查找实现,时间复杂度为 O(log N)。
    在这里插入图片描述

每次插入数据或者删除数据都会根据二分法进行查找,找到合适位置插入或者找到值进行删除,后续元素向前填充。

数组扩容情况:

  • 申请的数组也是动态的,即开始有个初始容量,当数组不足以继续插入的时候就会扩容,以二倍速度进行扩容。
  • 编码升级,例如当前编码为INTSET_ENC_INT16类型,但是插入了一个超过这个容量的数据,则会升级为合适的类型,重新分配内存进行迁移。这个扩容是不可逆的

整数数组只能进行升级不能进行降级。例如整数数组开始都是INTSET_ENC_INT16,增加了一个INTSET_ENC_INT32类型的整数,这个时候整个数组都会升级为INTSET_ENC_INT32类型,即已经存在的原本为INTSET_ENC_INT16类型的整数也转换为INTSET_ENC_INT32类型。当新增的INTSET_ENC_INT32的那个整数删除,其余原本为INTSET_ENC_INT16类型的整数升级之后也不能降级。

6.2 整数集合API

功能分类API 名称功能描述示例代码
创建与初始化intsetNew创建一个新的空整数集合。iintsetNew();
插入操作intsetAdd向整数集合中插入一个新元素。 intsetAdd(is, 100, &success);
删除操作intsetRemove从整数集合中删除一个指定的元素。 intsetRemove(is, 100, &success);
查找操作intsetFind检查整数集合中是否存在指定的元素。intsetFind(is, 100)
获取元素intsetGet获取整数集合中指定位置的元素。intsetGet(is, 0, &value))
集合长度intsetLen获取整数集合中元素的数量。intsetLen(is);
内存大小intsetBlobLen获取整数集合占用的内存大小(字节数)。intsetBlobLen(is);
编码相关intsetGetEncoding获取整数集合当前使用的编码方式。intsetGetEncoding(is);
其他操作intsetResize调整整数集合的容量。(通常由 Redis 内部调用,用户一般不需要直接使用)
其他操作intsetUpgradeAndAdd升级整数集合的编码方式并插入新元素。(通常由 Redis 内部调用,用户一般不需要直接使用)
销毁操作intsetDestroy释放整数集合占用的内存。intsetDestroy(is);

7、压缩列表

压缩列表是列表、有序集合、哈希的底层实现,前提是满足数量不太多并且单个元素不大的情况下。

7.1 压缩列表定义

Ziplist 是一个连续的字节数组,其结构如下:

<zlbytes> <zltail> <zllen> <entry1> <entry2> ... <entryN> <zlend>
字段名长度(字节)描述
zlbytes4整个 Ziplist 占用的字节数(包括自身)。
zltail4指向最后一个元素的偏移量(从 Ziplist 起始位置开始计算),用于快速定位尾部元素。
zllen2Ziplist 中的元素数量。如果元素数量超过 2^16-1,则需要遍历整个 Ziplist 来计算实际数量。
entryX可变 实际存储的元素,每个元素由元数据和数据组成。
zlend1标志 Ziplist 结束的特殊字节,固定为 0xFF。

每个Entry的数据结构:

<prevlen> <encoding> <data>
字段名描述
prevlen前一个元素的长度(用于反向遍历)。如果前一个元素长度小于 254 字节,则占 1 字节。 如果前一个元素长度大于等于 254 字节,则占 5 字节(第 1 字节为 0xFE,后 4 字节存储实际长度)。
encoding数据的编码方式,表示当前元素的类型和长度。小整数或短字符串可以直接嵌入到编码中。较长的字符串或大整数需要额外的长度描述。
data实际存储的数据内容,可能是字符串或整数。

连锁更新:
压缩列表是连续的内存,所以每次插入元素都会重新分配内存,然后数据迁移。通过上面prevlen字段的说明,根据前一个长度的大小决定这个字段的长度。例如目前所有元素大小都是253,当新增一个元素插入在第一个位置,大小大于254。那么第二个元素大小就会超过254【因为第二个元素的prevlen字段从占用1个字节变成占用5个字节】,同样也会导致第三个元素超过254,以此类推下去。

哈希类型满足一定条件也可以存储为压缩列表,可是哈希是键值对的形式存在的,是怎么存储到压缩列表中的呢?哈希键值对按照k1 v1 k2 v2顺序存储到压缩列表,每个key、每个value都是一个独立的entry,当进行读取数据的时候,每次会读取两个压缩节点。

7.2 压缩列表的API

以下是 Redis 中压缩列表(Ziplist)相关的 API,以 Markdown 表格形式排版。

功能分类API 名称功能描述示例代码
创建与初始化ziplistNew创建一个新的空压缩列表。ziplistNew();
插入操作ziplistPush向压缩列表的头部或尾部插入一个新元素。ziplistPush(zl, (unsigned char*)"hello", 5, ZIPLIST_TAIL);
删除操作ziplistDelete删除压缩列表中指定位置的元素。unsigned char *p = ziplistIndex(zl, 0); zl = ziplistDelete(zl, &p);
查找操作ziplistFind在压缩列表中查找指定的值。ziplistFind(zl, ziplistIndex(zl, 0), (unsigned char*)"hello", 5, 0);
获取元素ziplistIndex获取压缩列表中指定索引位置的元素。ziplistIndex(zl, 0);
遍历元素ziplistNext获取当前元素的下一个元素。 ziplistNext(zl, p);
遍历元素ziplistPrev获取当前元素的上一个元素。ziplistPrev(zl, p);
获取值ziplistGet获取压缩列表中指定位置的值。unsigned char *sval; unsigned int slen; long lval; ziplistGet(p, &sval, &slen, &lval);
集合长度ziplistLen获取压缩列表中元素的数量。 ziplistLen(zl);
内存大小ziplistBlobLen获取压缩列表占用的内存大小(字节数)。ziplistBlobLen(zl);
销毁操作ziplistDeleteRange删除压缩列表中指定范围的元素。ziplistDeleteRange(zl, 0, 2);
其他操作ziplistCompare比较压缩列表中指定位置的值是否等于给定值。iziplistCompare(p, (unsigned char*)"hello", 5))
其他操作ziplistIncrRefCount增加压缩列表的引用计数(用于共享压缩列表)。(通常由 Redis 内部调用,用户一般不需要直接使用)
其他操作ziplistRelease释放压缩列表占用的内存。ziplistRelease(zl);

  1. 为什么强调对字符串修改之后的长度呢,为了防止原来字符串长度为1K,但是一下子拼接一个2M的字符串而导致的频繁申请内存。 ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/72579.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

由麻省理工学院计算机科学与人工智能实验室等机构创建低成本、高效率的物理驱动数据生成框架,助力接触丰富的机器人操作任务

2025-02-28&#xff0c;由麻省理工学院计算机科学与人工智能实验室&#xff08;CSAIL&#xff09;和机器人与人工智能研究所的研究团队创建了一种低成本的数据生成框架&#xff0c;通过结合物理模拟、人类演示和基于模型的规划&#xff0c;高效生成大规模、高质量的接触丰富型机…

计算机视觉|ViT详解:打破视觉与语言界限

一、ViT 的诞生背景 在计算机视觉领域的发展中&#xff0c;卷积神经网络&#xff08;CNN&#xff09;一直占据重要地位。自 2012 年 AlexNet 在 ImageNet 大赛中取得优异成绩后&#xff0c;CNN 在图像分类任务中显示出强大能力。随后&#xff0c;VGG、ResNet 等深度网络架构不…

SpringTask 引起的错误

SpringTask 引起的错误 1. 场景 在使用 SpringBoot 编写后台程序时&#xff0c;当在浏览器页面中发起请求时&#xff0c;MP 自动填充来完成一些字段的填充&#xff0c;例如创建时间、创建人、更新时间、更新人等。但是当编写微信小程序时&#xff0c;由于一些字段无法进行自动…

FPGA学习篇——Verilog学习4

1.1 结构语句 结构语句主要是initial语句和always语句&#xff0c;initial 语句它在模块中只执行一次&#xff0c;而always语句则不断重复执行&#xff0c;以下是一个比较好解释的图: (图片来源于知乎博主罗成&#xff0c;画的很好很直观&#xff01;) 1.1.1 initial语句 ini…

【Linux】【网络】UDP打洞-->不同子网下的客户端和服务器通信(未成功版)

【Linux】【网络】UDP打洞–>不同子网下的客户端和服务器通信&#xff08;未成功版&#xff09; 上次说基于UDP的打洞程序改了五版一直没有成功&#xff0c;要写一下问题所在&#xff0c;但是我后续又查询了一些资料&#xff0c;成功实现了&#xff0c;这次先写一下未成功的…

【Python编程】高性能Python Web服务部署架构解析

一、FastAPI 与 Uvicorn/Gunicorn 的协同 1. 开发环境&#xff1a;Uvicorn 直接驱动 作用&#xff1a;Uvicorn 作为 ASGI 服务器&#xff0c;原生支持 FastAPI 的异步特性&#xff0c;提供热重载&#xff08;--reload&#xff09;和高效异步请求处理。 启动命令&#xff1a; u…

前端权限流程(基于rbac实现思想)

1. 权限控制 1.1. 实现思想 基于rbac权限控制思想实现&#xff0c;给用户分配角色&#xff0c;给角色分配权限 给用户分配角色业务 注意&#xff1a;上方图片是个示例图&#xff0c;代表给用户分配职位(角色)&#xff0c;页面中使用了Element-plus的el- checkbox组件…

软件高级架构师 - 软件工程

补充中 测试 测试类型 静态测试 动态测试 测试阶段 单元测试中&#xff0c;包含性能测试&#xff0c;如下&#xff1a; 集成测试中&#xff0c;包含以下&#xff1a; 维护 遗留系统处置 高水平低价值&#xff1a;采取集成 对于这类系统&#xff0c;采取 集成 的方式&…

python3.13安装教程【2025】python3.13超详细图文教程(包含安装包)

文章目录 前言一、python3.13安装包下载二、Python 3.13安装步骤三、Python3.13验证 前言 本教程将为你详细介绍 Python 3.13 python3.13安装教程&#xff0c;帮助你顺利搭建起 Python 3.13 开发环境&#xff0c;快速投身于 Python 编程的精彩实践中。 一、python3.13安装包下…

【极客时间】浏览器工作原理与实践-2 宏观视角下的浏览器 (6讲) - 2.5 渲染流程(上):HTML、CSS和JavaScript,是如何变成页面的?

https://time.geekbang.org/column/article/118205 2.5 渲染流程&#xff08;上&#xff09;&#xff1a;HTML、CSS和JavaScript&#xff0c;是如何变成页面的&#xff1f; 2.4讲了导航相关的流程&#xff0c;那导航被提交后又会怎么样呢&#xff1f; 就进入了渲染阶段。 这…

CMake学习笔记(一):工程的新建和如何将源文件生成二进制文件

cmake是我们在工作过程中比较常见的一个工具&#xff0c;该系列文章是自己用来学习的笔记。目前只是记录下自己学习cmake的过程中的一些重要的知识点&#xff0c;其是以项目需求为导向并非完整的cmake的学习路线和系统&#xff0c;同样也并非适合所有的人。 1.生成一个可执行文…

重定位(1)

一、重定位 1、对于有强大ROM的板子&#xff0c;他们会将上电后的程序放到指定RAM内存 2、无强大片内ROM的板子&#xff0c;自己编程序让他知道RAM内存指定位置 指定位置&#xff1a;就是链接地址&#xff0c;指定哪里&#xff0c;哪里就被编译好一块内存用来存放上电的程序 …

自由学习记录(41)

代理服务器的核心功能是在客户端&#xff08;用户设备&#xff09;和目标服务器&#xff08;网站/资源服务器&#xff09;之间充当“中介”&#xff0c;具体过程如下&#xff1a; 代理服务器的工作流程 当客户端希望访问某个网站&#xff08;比如 example.com&#xff09;时&…

Linux+apache之 浏览器访问云服务器磁盘的图片,通过tomcat

https://javab.blog.csdn.net/article/details/80580520 安装tomcact 修改添加 <Context docBase"/home/wyp/images" path"/img" debug"0" reloadable"true" />修改完成后保存重启tomcat服务。 测试访问方式&#xff1a;http…

LIUNX学习-线程

线程概念 一个进程需要访的大部分资源&#xff0c;诸如自身的代码、数据、new\malloc的空间数据、命令行参数和环境变量、动态库、甚至是系统调用访问内核代码…都是通过虚拟地址空间来访问的。换而言之&#xff0c;进程地址空间是进程的资源窗口&#xff01;&#xff01;   …

1.Big-endian/ little endian大端对齐、小端对齐

一、大端模式、小端模式的介绍 Little endian&#xff1a;是低位字节排放在内存的低地址端、高位字节排放在内存的高地址端。 Big-endian&#xff1a;是高位字节排放在内存的低地址端、低位字节排放在内存的高地址端。 西门子是大端模式&#xff0c;因为比如 MW100 MB100(高位…

SpringBoot Actuator

SpringBoot Actuator 一、简介二、入门1、依赖2、默认监控指标3、查询监控指标4、全量监控指标 三、Spring Boot Admin1、主要功能2、Admin3、Client4、应用墙5、其他 四、定制化1、定制Health端点2、定制Info端点3、定制Metrics端点4、定制Endpoint端点 一、简介 SpringBoot自…

06 HarmonyOS Next性能优化之LazyForEach 列表渲染基础与实现详解 (一)

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; 目录 一、代码结构概览二、详细代码解析1. 数据源管理实现2. 数据结构定义3. 优化的列表项组件4. 主列表组件实现 一、代码结构概览 本文将详细解…

vscode 查看3d

目录 1. vscode-3d-preview obj查看ok 2. vscode-obj-viewer 没找到这个插件&#xff1a; 3. 3D Viewer for Vscode 查看obj失败 1. vscode-3d-preview obj查看ok 可以查看obj 显示过程&#xff1a;开始是绿屏&#xff0c;过了1到2秒&#xff0c;后来就正常看了。 2. vsc…

excel 斜向拆分单元格

右键-合并单元格 右键-设置单元格格式-边框 在设置好分割线后&#xff0c;你可以开始输入文字。 需要注意的是&#xff0c;文字并不会自动分成上下两行。 为了达到你期望的效果&#xff0c;你可以通过 同过左对齐、上对齐 空格键或使用【AltEnter】组合键来调整单元格中内容的…