Redis设计与实现-数据结构
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可以查看键值对的值的编码方式】:
数据类型 | 编码方式 | 底层实现 | 切换条件 |
---|---|---|---|
String | RAW | 简单动态字符串(SDS) | 当字符串的长度大于32字节,始终使用 SDS。 |
EMBSTR | 简单动态字符串(SDS) | 当字符串的长度小于等于32字节,使用 embstr 编码。 | |
INT | 整数 | 当存储的值可以表示为 64 位有符号整数时,使用 INT 编码。 | |
List | ZIPLIST | 压缩列表(Ziplist) | 元素数量 < list-max-ziplist-entries (默认 512)且每个元素大小 < list-max-ziplist-value (默认 64 字节)。 |
LINKEDLIST | 双向链表 | 不满足上述条件时,切换为双向链表。 | |
Hash | ZIPLIST | 压缩列表(Ziplist) | 字段数量 < hash-max-ziplist-entries (默认 512)且每个字段大小 < hash-max-ziplist-value (默认 64 字节)。 |
HASHTABLE | 哈希表 | 不满足上述条件时,切换为哈希表。 | |
Set | INTSET | 整数集合(IntSet) | 元素数量 < set-max-intset-entries (默认 512)且所有元素为整数时,使用 IntSet。 |
HASHTABLE | 哈希表 | 不满足上述条件时,切换为哈希表。 | |
ZSet | ZIPLIST | 压缩列表(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语言的区别
-
获取字符串的长度时间复杂度
因为SDS存储了字符串的长度,所以时间复杂度为O(1);而C语言只存储了字符串本身,所以需要变量整个字符串才知道字符串的长度,时间复杂度为O(N) -
杜绝缓冲区溢出
例如,字符串拼接函数strcat(s1, s2)将字符串s2拼接到s1后面,但是在拼接的时候没有检测s1的内存,s1没有足够的内存【s1后面存在其他有用的数据s3】,那么拼接就会导致s2会把s3的部分数据给修改了。而SDS空间分配策略就杜绝了这个问题出现。SDS API首先会检测空间是否满足,如果不满足自动的空间扩展。 -
减少修改字符串导致的重分配次数
C语言针对于一个长度为N的字符串底层是一个N+1的字符数组,每次修改字符串都需要进行内存重分配。增加字符需要扩展内存【否则,出现内存溢出问题】,减少字符需要释放内存【否则,出现内存泄漏问题】。执行内存重分配就需要系统调用,比较耗时,而Redis作为存储设备,字符串变更更是家常便饭。
Redis通过未使用空间解耦了字符串长度和底层数据之间的绑定关系,通过未使用空间就实现了空间预分配和惰性空间释放。 -
二进制安全
C语言字符串必须符合某种编码,而且除了字符串的末尾为空字符之外,其他地方不允许有空字符,否则字符串就会提前结束。这就导致C语言只能存储文本数据,不能保存像图片、视频、音频等二进制数据。而SDS是根据len属性的长度确定结束,所以没有上述问题。 -
兼容部分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>
字段名 | 长度(字节) | 描述 |
---|---|---|
zlbytes | 4 | 整个 Ziplist 占用的字节数(包括自身)。 |
zltail | 4 | 指向最后一个元素的偏移量(从 Ziplist 起始位置开始计算),用于快速定位尾部元素。 |
zllen | 2 | Ziplist 中的元素数量。如果元素数量超过 2^16-1,则需要遍历整个 Ziplist 来计算实际数量。 |
entry | X | 可变 实际存储的元素,每个元素由元数据和数据组成。 |
zlend | 1 | 标志 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); |
为什么强调对字符串修改之后的长度呢,为了防止原来字符串长度为1K,但是一下子拼接一个2M的字符串而导致的频繁申请内存。 ↩︎
相关文章:
Redis设计与实现-数据结构
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 哈希表扩缩的判断依…...
Ubuntu20.04双系统安装及软件安装(四):国内版火狐浏览器
Ubuntu20.04双系统安装及软件安装(四):国内版火狐浏览器 Ubuntu系统会自带火狐浏览器,但该浏览器不是国内版的,如果平常有记录书签、浏览记录、并且经常使用浏览器插件的习惯,建议重装火狐浏览器为国内版的…...
C语言100天练习题【记录本】
C语言经典100题(手把手 编程) 可以在哔哩哔哩找到 已解决的天数:一,二,五,六 下面的都是模模糊糊的 可以学学这些算法,我是算法白痴,但是我不是白痴,可以学ÿ…...
基于CURL命令封装的JAVA通用HTTP工具
文章目录 一、简要概述二、封装过程1. 引入依赖2. 定义脚本执行类 三、单元测试四、其他资源 一、简要概述 在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具,可以说是一款很强大的http命令行工具。它支持文件的上传和下载,是综合传输工具&…...
SQL刷题:自连接(Self-Join)--通过将 同一张表连接两次,比较不同行之间的数据关系
例题: 表:Employee ---------------------- | Column Name | Type | ---------------------- | id | int | | name | varchar | | salary | int | | managerId | int | ---------------------- id 是该表的主键…...
避坑!用Docker搞定PHP开发环境搭建(Mac、Docker、Nginx、PHP-FPM、XDebug、PHPStorm、VSCode)
本次更新主要是对环境版本进行了更新,例如php 7.3.7升级到了7.3.8,另外之前的版本有同学踩了坑,主要是官方docker镜像php:7.3.7-fpm和php:7.3.8-fpm使用了不同版本的debian,后面会提到,请各位同学留意。 因为最近换电脑…...
第七节:基于Winform框架的串口助手小项目---协议解析《C#编程》
介绍 文章上所说的串口助手,工程文件资源-CSDN文库 目标 代码实现 private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e){if (isRxShow false) return;// 1,需要读取有效的数据 BytesToReadbyte[] dataTemp new byte[serialPor…...
pt-archiver删除数据库的数据表/各种报错类型
这篇帖子是前面文的一部分延申 mysqlimport导入一亿数据的csv文件/一行命令删除表-CSDN博客 如需转载,标记出处 目录 pt-archiver命令格式 如果执行后出现下面报错 1)Cannot find an ascendable index in table at /usr/bin/pt-archiver line 3233. …...
STM32Cubemx配置E22-xxxT22D lora模块实现定点传输
文章目录 一、STM32Cubemx配置二、定点传输**什么是定点传输?****定点传输的特点****定点传输的工作方式****E22 模块定点传输配置****如何启用定点传输?****示例** **应用场景****总结** **配置 1:C0 00 07 00 02 04 62 00 17 40****解析** …...
模块和端口
1、模块 模块内部的5个组成是:变量声明 数据流语句 低层模块实例 函数和任务 行为语句 SR锁存器 timescale 1ns / 1psmodule SR_latch(input wire Sbar ,input wire Rbar ,output wire Q ,output wire Qbar);nand…...
Android+SpringBoot的老年人健康饮食小程序平台
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,我会一一回复,希望帮助更多的人。 系统介绍 我将从经济、生活节奏、技术融合等方面入手,详细阐述居家养老管理模式兴起的…...
[machine learning] MACS、MACs、FLOPS、FLOPs
本文介绍机器学习中衡量一个模型计算复杂度的四个指标:MACS、MACs、FLOPS、FLOPs。 首先从含义上讲,可以分类两类:MACS/FLOPS和MACs/FLOPs。MACs/FLOPs表示总的操作数(后缀s可以看成是表示复数),MACS/FLOPS表示每秒可以执行的操作…...
PostgreSQL10 物理流复制实战:构建高可用数据库架构!
背景 PostgreSQL 10 在高可用架构中提供了物理复制,也称为流复制(Streaming Replication),用于实现实例级别的数据同步。PostgreSQL 复制机制主要包括物理复制和逻辑复制:物理复制依赖 WAL 日志进行物理块级别的同步&…...
STM32---FreeRTOS中断管理试验
一、实验 实验目的:学会使用FreeRTOS的中断管理 创建两个定时器,一个优先级为4,另一个优先级为6;注意:系统所管理的优先级范围 :5~15 现象:两个定时器每1s,打印一段字符串&#x…...
Linux常见操作命令(1)
(一)常用命令: 1.Tab 键可以实现自动补齐和提示,要合理使用 2.方向键(上下)来切换前后执行过的命令 (二)查看命令 一共有三个:ls, cd , pwd 。 1.ls:列出目录内容,包括参数-l(详细…...
SPI驱动(二) -- SPI驱动程序模型
文章目录 参考资料:一、SPI驱动重要数据结构1.1 SPI控制器数据结构1.2 SPI设备数据结构1.3 SPI驱动数据结构 二、SPI 驱动框架2.1 SPI控制器驱动程序2.2 SPI设备驱动程序 三、总结 参考资料: 内核头文件:include\linux\spi\spi.h 一、SPI驱…...
Qt中txt文件输出为PDF格式
main.cpp PdfReportGenerator pdfReportGenerator;// 加载中文字体if (QFontDatabase::addApplicationFont(":/new/prefix1/simsun.ttf") -1) {QMessageBox::warning(nullptr, "警告", "无法加载中文字体");}// 解析日志文件QVector<LogEntr…...
SpringBoot 校园新闻网站
收藏关注不迷路!! 🌟文末获取源码数据库🌟 感兴趣的可以先收藏起来,还有大家在毕设选题(免费咨询指导选题),项目以及论文编写等相关问题都可以给我留言咨询,希望帮助更多…...
JAVA面经2
ConcurrentHashMap 并发程序出现问题的根本原因 线程池 线程池的执行原理(核心参数) 线程池的常见阻塞队列 ArrayBlockingQueue插入和删除数据,只采用了一个lock,而LinkedBlockingQueue则是在插入和删除分别采用了putLock和takeL…...
NVIDIA(英伟达) GPU 芯片架构发展史
GPU 性能的关键参数 CUDA 核心数量(个):决定了 GPU 并行处理能力,在 AI 等并行计算类业务下,CUDA 核心越多性能越好。 显存容量(GB):决定了 GPU 加载数据量的大小,在 AI…...
C++设计一:日期类Date实现
一、引言与概述 1 引言 日期操作是软件开发中的常见需求,如日程管理、数据统计等场景均需处理日期的比较、偏移及合法性校验。为简化此类操作,本文设计了一个高效且类型安全的C日期类Date。 该类通过构造函数内嵌合法性检查,确保对象初始状…...
关于2023新版PyCharm的使用
考虑到大家AI编程的需要,建议大家安装新版Python解释器和新版PyCharm,下载地址都可以官网进行: Python:Download Python | Python.org(可以根据需要自行选择,建议选择3.11,保持交流版本一致&am…...
【Azure 架构师学习笔记】- Azure Databricks (15) --Delta Lake 和Data Lake
本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Databricks】系列。 接上文 【Azure 架构师学习笔记】- Azure Databricks (14) – 搭建Medallion Architecture part 2 前言 ADB 除了UC 这个概念之外,前面【Azure 架构师学习笔记】- Azure Databricks (1…...
一文了解Conda使用
一、Conda库频道 conda的软件频道是存储软件包的远程位置,当在Conda中安装软件包时,它会从指定的频道中下载和提取软件包。频道包含了各种软件包,不同的频道可能提供不同版本的软件包,用户可以根据需要选择适合的版本。 常见 Co…...
SP导入智能材质球
智能材质球路径 ...\Adobe Substance 3D Painter\resources\starter_assets\smart-materials 放入之后就会自动刷新...
记录一次Spring事务失效导致的生产问题
一、背景介绍 公司做的是“聚合支付”业务,对接了微信、和包、数字人民币等等多家支付机构,我们提供统一的支付、退款、自动扣款签约、解约等能力给全国的省公司、机构、商户等。 同时,需要做对账功能,即支付机构将对账文件给到…...
腾讯云物联网平台(IoT Explorer)设备端使用
1、直接看图流程 2、跑起来demo,修改产品id,设备名称,设备秘钥。 3、连接部分 4、修改默认地址和端口 sdk里面的地址默认是带着产品ID拼接的,咱们现在中铁没有泛域名解析,要改下这里。把+productID都去掉,然后地址里的.也去掉。...
ML.NET库学习023: ONNX Runtime 中 C++ 辅助函数解析:Span 类与张量操作
文章目录 ML.NET库学习023: ONNX Runtime 中 C 辅助函数解析:Span 类与张量操作主题项目主要目的和原理项目概述实现的主要功能关键函数代码结构 主要功能与步骤Span 类的实现张量大小计算数据加载与处理准确性评估 数据集的使用以下是逐步解释ÿ…...
利用opencv_python(pdf2image、poppler)将pdf每页转为图片
1、安装依赖pdf2image pip install pdf2image 运行.py报错,因为缺少了poppler支持。 2、安装pdf2image的依赖poppler 以上命令直接报错。 改为手工下载: github: Releases oschwartz10612/poppler-windows GitHub 百度网盘: 百度网盘…...
告别GitHub连不上!一分钟快速访问方案
一、当GitHub抽风时,你是否也这样崩溃过? 😡 npm install卡在node-sass半小时不动😭 git clone到90%突然fatal: early EOF🤬 改了半天hosts文件,第二天又失效了... 根本原因:传统代理需要复杂…...
学习DeepSeek V3 与 R1 核心区别(按功能维度分类)
一、定位与架构 V3(通用型模型) 定位:多模态通用大模型,擅长文本生成、多语言翻译、智能客服等多样化任务12。架构:混合专家(MoE)架构,总参数 6710 亿,每次…...
Linux总结
1 用户与用户组管理 1.1 用户与用户组 //linux用户和用户组 Linux系统是一个多用户多任务的分时操作系统 使用系统资源的用户需要账号进入系统 账号是用户在系统上的标识,系统根据该标识分配不同的权限和资源 一个账号包含用户和用户组 //用户分类 超级管理员 UID…...
web高可用集群项目(数据库主从同步、文件共享存储、nginx动静分离+负载均衡+高可用)
一、项目环境 二、环境准备 主机名IP地址备注openEuler-1192.168.121.11主负载调度器openEuler-2192.168.121.12副负载调度器openEuler-3192.168.121.13web-1(静态)openEuler-4192.168.121.14web-2(静态)openEuler-5192.168.121.…...
如何快速上手RabbitMQ 笔记250304
如何快速上手RabbitMQ 要快速上手 RabbitMQ,可以按照以下步骤进行,从安装到基本使用逐步掌握核心概念和操作: 1. 理解核心概念 Producer(生产者):发送消息的程序。Consumer(消费者)…...
PPT小黑第26套
对应大猫28 层次级别是错的,看着是十页,导入ppt之后四十多页 选中所有 红色蓝色黑色 文本选择标题:选择 -格式相似文本(检查有没有漏选 漏选的话 按住ctrl 点下一个) 要求新建幻灯片中不包含原素材中的任何格式&…...
甘特图开发代码(测试版)
场景:要实现的功能就是单行数据能左右拖动。 流程五个:ABCDE。(对应:Charter开发、概念和计划、初样开发、正样开发、验证) 1、A有开始时间,结束时间。B的开始时间必须是A的结束时间(相等或者…...
鸿蒙与DeepSeek深度整合:构建下一代智能操作系统生态
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 https://www.captainbed.cn/north 目录 技术融合背景与价值鸿蒙分布式架构解析DeepSeek技术体系剖析核心整合架构设计智能调度系统实现…...
Docker Desktop常见问题记录
1.docker pull报错,无法连接https://registry-1.docker.io/v2/ 报错信息如下: Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection(Client.Timeout exceeded …...
Qt 的 Lambda 捕获局部变量导致 UI 更新异常的分析与解决
1. 问题描述 在 Qt 开发中,我们通常会使用 QTimer 进行周期性 UI 更新。例如,下面的代码用于在检测游戏窗口时,在 UI 界面上显示动态变化的“正在检测游戏窗口...”的文本,每 300 毫秒更新一次。 void MainWindow::detectAndPopulateGameList() {ui->game_record_stac…...
RAGflow采用docker-compose-continuous方式pull,把服务器充满了
采用docker-compose-continuous在后台下载,导致服务器被充满。 原因分析: 如果网络不稳定,可能导致下载任务异常中断,而 systemd 服务会不断重启并重新下载,从而占用大量空间。如果网络问题无法解决,可以…...
【第12节】C++设计模式(结构型模式)-Proxy(代理)模式
一、问题背景 使用 Proxy 模式优化对象访问 在某些情况下,直接访问对象可能会导致性能问题或安全性问题。Proxy 模式(代理模式)通过引入一个代理对象来控制对原始对象的访问,从而解决这些问题。以下是几种典型的应用场景…...
【C++】vector(上):vector的常用接口介绍
文章目录 前言一、vector的介绍二、vector的常用接口介绍1.vector类对象的常见构造2.vector iterator 的使用3.vector类对象的容量操作3.1 size、capacity 和 empty的使用3.2 reserve的使用3.3 resize的使用 4.vector类对象的访问(包含data:返回底层数组…...
【详细讲解在STM32的UART通信中使用DMA机制】
详细讲解在STM32的UART通信中使用DMA机制 目录 详细讲解在STM32的UART通信中使用DMA机制一、DMA机制概述二、DMA在UART中的作用三、DMA的配置步骤四、UART初始化与DMA结合五、DMA传输的中断处理六、DMA与中断的结合使用七、注意事项与常见问题八、代码示例九、总结 一、DMA机制…...
极狐GitLab 17.9 正式发布,40+ DevSecOps 重点功能解读【三】
GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料: 极狐GitLab 官网极狐…...
快速生成viso流程图图片形式
我们在写详细设计文档的过程中总会不可避免的涉及到时序图或者流程图的绘制,viso这个软件大部分技术人员都会使用,但是想要画的好看,画的科学还是比较难的,现在我总结一套比较好的方法可以生成好看科学的viso图(图片格式)。主要思…...
设备管理系统功能与.NET+VUE(IVIEW)技术实现
在现代工业和商业环境中,设备管理系统(Equipment Management System,简称EMS)是确保设备高效运行和维护的关键工具。本文采用多租户设计的设备管理系统,基于.NET后端和VUE前端(使用IVIEW UI框架)…...
《深度学习实战》第11集:AI大模型压缩与加速
深度学习实战 | 第11集:AI大模型压缩与加速 在深度学习领域,随着模型规模的不断增大,模型的推理速度和部署效率成为实际应用中的关键挑战。本篇博客将带你深入了解模型压缩与加速的核心技术,并通过一个实战项目展示如何使用知识蒸…...
【大模型安全】大模型的技术风险
【大模型安全】大模型的技术风险 1.DDoS攻击2.常见的传统网络攻击方式3.恶意意图的识别4.AI生成虚假信息传播5.利用AI进行黑客攻击6.模型对抗攻击7.后门攻击8.Prompt攻击9.数据投毒攻击10.模型窃取攻击11.数据窃取攻击 1.DDoS攻击 2023年11月9日凌晨,OpenAI在官网公…...
git命令学习记录
1. git reset 参数说明 git reset 是用来回退版本的,它可以添加三个参数,常用的使用格式是这样的:git reset [--hard | --soft | --mixed] 版本号 一般使用git修改文件并提交需要三步,第一步在文本编辑器中编辑文件,也…...
Gartner:数据安全平台DSP提升数据流转及使用安全
2025 年 1 月 7 日,Gartner 发布“China Context:Market Guide for Data Security Platforms”(《数据安全平台市场指南——中国篇》,以下简称指南),报告主要聚焦中国数据安全平台(Data Securit…...