Redis-底层数据结构

Redis-底层数据结构

        • redisObject对象机制
            • 对象共享
            • 引用计数以及对象的消毁
        • 动态字符串SDS
        • 链表
          • 链表的优缺点:
        • 压缩链表
          • ziplist的缺点
        • 字典-Dict
          • rehash
          • 渐进式rehash
        • 整数集-intSet
          • 内存分布图
          • 整数集合的升级
        • 跳表 - ZSkipList
        • 快表-quicklist
        • listpack

redisObject对象机制

在这里插入图片描述

typedef struct redisObject{// 类型unsigned type:4;// 编码方式unsigned encoding:4;// LRU - 24位, 记录最末一次访问时间(相对于lru_clock); 或者 LFU(最少使用的数据:8位频率,16位访问时间)unsigned lru:LRU_BITS; // LRU_BITS: 24// 引用计数int refcount;// 指向底层数据结构实例void *ptr;} robj;

为什么Redis会设计redisObject对象?

在redis的命令中,用于对键进行处理的命令占了很大一部分,而对于键所保存的值的类型(键的类型),键能执行的命令又各不相同。如: LPUSHLLEN 只能用于列表键, 而 SADDSRANDMEMBER 只能用于集合键, 等等; 另外一些命令, 比如 DELTTLTYPE, 可以用于任何类型的键;但是要正确实现这些命令, 必须为不同类型的键设置不同的处理方式: 比如说, 删除一个列表键和删除一个字符串键的操作过程就不太一样。

以上的描述说明, Redis 必须让每个键都带有类型信息, 使得程序可以检查键的类型, 并为它选择合适的处理方式

为了解决以上问题, Redis 构建了自己的类型系统, 这个系统的主要功能包括:

  • redisObject 对象.

  • 基于 redisObject 对象的类型检查.

  • 基于 redisObject 对象的显式多态函数.

  • 对 redisObject 进行分配、共享和销毁的机制

  • type记录了对象所保存的值的类型,它的值可能是以下常量中的一个:

/*
* 对象类型
*/
#define OBJ_STRING 0 // 字符串
#define OBJ_LIST 1 // 列表
#define OBJ_SET 2 // 集合
#define OBJ_ZSET 3 // 有序集
#define OBJ_HASH 4 // 哈希表
  • encoding记录了对象所保存的值的编码,它的值可能是以下常量中的一个:
/*
* 对象编码
*/
#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* 注意:版本2.6后不再使用. */
#define OBJ_ENCODING_LINKEDLIST 4 /* 注意:不再使用了,旧版本2.x中String的底层之一. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

redis执行命令的过程
在这里插入图片描述

对象共享

redis一般会把一些常见的值放到一个共享对象中,这样可使程序避免了重复分配的麻烦,也节约了一些CPU时间。

redis预分配的值对象如下

  • 各种命令的返回值,比如成功时返回的OK,错误时返回的ERROR,命令入队事务时返回的QUEUE,等等
  • 包括0 在内,小于REDIS_SHARED_INTEGERS的所有整数(REDIS_SHARED_INTEGERS的默认值是10000)

img

引用计数以及对象的消毁

redisObject中有refcount属性,是对象的引用计数,显然计数0那么就是可以回收。

  • 每个redisObject结构都带有一个refcount属性,指示这个对象被引用了多少次;
  • 当新创建一个对象时,它的refcount属性被设置为1;
  • 当对一个对象进行共享时,redis将这个对象的refcount加一;
  • 当使用完一个对象后,或者消除对一个对象的引用之后,程序将对象的refcount减一;
  • 当对象的refcount降至0 时,这个RedisObject结构,以及它引用的数据结构的内存都会被释放。
动态字符串SDS

redis中保存的key是字符串,value往往是字符串或者字符串的集合,可见字符串是redis中最常用的一种数据结构

C语言字符串问题:c语言字符串本质是一个字符数组

  • 获取字符串长度需要通过运算(O(n))
  • 非二进制安全(字符数组末尾会有个’\0’来标记结束)
  • 不可修改

为此redis构建了一种新的字符串结构,称为简单动态字符串简称:SDS

在这里插入图片描述

  • len,记录了字符串长度。这样获取字符串长度的时候,只需要返回这个成员变量值就行,时间复杂度只需要 O(1)。
  • alloc,分配给字符数组的空间长度。这样在修改字符串的时候,可以通过 alloc - len 计算出剩余的空间大小,可以用来判断空间是否满足修改需求,如果不满足的话,就会自动将 SDS 的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用 SDS 既不需要手动修改 SDS 的空间大小,也不会出现前面所说的缓冲区溢出的问题。
  • flags,用来表示不同类型的 SDS。一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64,后面在说明区别之处。
  • buf[],字符数组,用来保存实际数据。不仅可以保存字符串,也可以保存二进制数据。

Redis 一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64。

这 5 种类型的主要区别就在于,它们数据结构中的 len 和 alloc 成员变量的数据类型不同。

比如 sdshdr8它的定义分别如下:

//__attribute__ ((packed)) ,它的作用是:告诉编译器取消结构体在编译过程中的优化对齐,按照实际占用字节数进行对齐。
struct __attribute__ ((__packed__)) sdshdr8{uint8_t len;uint8_t alloc; unsigned char flags; char buf[];
};

SDS扩容原理:

  • 如果所需的 sds 长度小于 1 MB,那么最后的扩容是按照翻倍扩容来执行的,即 2 倍的newlen
  • 如果所需的 sds 长度超过 1 MB,那么最后的扩容长度应该是 newlen + 1MB

这样的好处是,下次在操作 SDS 时,如果 SDS 空间够的话,API 就会直接使用「未使用空间」,而无须执行内存分配,有效的减少内存分配次数

链表

常见的双向链表的实现方式

typedef struct listNode {//前置节点struct listNode *prev;//后置节点struct listNode *next;//节点的值void *value;
} l

redis在listNode结构体基础上又封装了list

typedef struct list {//链表头节点listNode *head;//链表尾节点listNode *tail;//节点值复制函数void *(*dup)(void *ptr);//节点值释放函数void (*free)(void *ptr);//节点值比较函数int (*match)(void *ptr, void *key);//链表节点数量unsigned long len;
} list
链表的优缺点:

优点:

  • listNode 链表节点的结构里带有 prev 和 next 指针,获取某个节点的前置节点或后置节点的时间复杂度只需O(1),而且这两个指针都可以指向 NULL,所以链表是无环链表
  • list 结构因为提供了表头指针 head 和表尾节点 tail,所以获取链表的表头节点和表尾节点的时间复杂度只需O(1)
  • list 结构因为提供了链表节点数量 len,所以获取链表中的节点数量的时间复杂度只需O(1)
  • listNode 链表节使用 void* 指针保存节点值,并且可以通过 list 结构的 dup、free、match 函数指针为节点设置该节点类型特定的函数,因此链表节点可以保存各种不同类型的值

缺点:

  • 链表每个节点之间的内存都是不连续的,意味着无法很好利用 CPU 缓存。能很好利用 CPU 缓存的数据结构就是数组,因为数组的内存是连续的,这样就可以充分利用 CPU 缓存来加速访问。
  • 还有一点,保存一个链表节点的值都需要一个链表节点结构头的分配,内存开销较大

因此,Redis 3.0 的 List 对象在数据量比较少的情况下,会采用「压缩列表」作为底层数据结构的实现,它的优势是节省内存空间,并且是内存紧凑型的数据结构。

不过,压缩列表存在性能问题(具体什么问题,下面会说),所以 Redis 在 3.2 版本设计了新的数据结构 quicklist,并将 List 对象的底层数据结构改由 quicklist 实现。

然后在 Redis 5.0 设计了新的数据结构 listpack,沿用了压缩列表紧凑型的内存布局,最终在最新的 Redis 版本,将 Hash 对象和 Zset 对象的底层数据结构实现之一的压缩列表,替换成由 listpack 实现。

压缩链表

压缩列表是 Redis 为了节约内存而开发的,它是由连续内存块组成的顺序型数据结构,有点类似于数组。

img

  • zlbytes字段的类型是uint32_t, 这个字段中存储的是整个ziplist所占用的内存的字节数

  • zltail字段的类型是uint32_t, 它指的是ziplist中最后一个entry的偏移量. 用于快速定位最后一个entry, 以快速完成pop等操作

  • zllen字段的类型是uint16_t, 它指的是整个ziplit中entry的数量. 这个值只占2bytes(16位): 如果ziplist中entry的数目小于65535(2的16次方), 那么该字段中存储的就是实际entry的值. 若等于或超过65535, 那么该字段的值固定为65535, 但实际数量需要一个个entry的去遍历所有entry才能得到.

  • zlend是一个终止字节, 其值为全F, 即0xff. ziplist保证任何情况下, 一个entry的首字节都不会是255

    前三这个都采用小端的存储方式

常见的entry的结构为** **

  • prevlen:前一个entry的大小 为了实现从后往前遍历;

​ 当前一个元素长度小于254(255用于zlend)的时候,prevlen长度为1个字节,值即为前一个entry的长度,如果长 度大于等于254的时候,prevlen用5个字节表示,第一字节设置为254,后面4个字节存储一个小端的无符号整 型,表示前一个entry的长度;

  • encoding:不同的情况下值不同,用于表示当前entry的类型和长度;

    如果当前节点的数据是整数,则 encoding 会使用 1 字节的空间进行编码,也就是 encoding 长度为 1 字节。通过 encoding 确认了整数类型,就可以确认整数数据的实际大小了,比如如果 encoding 编码确认了数据是 int16 整数,那么 data 的长度就是 int16 的大小。

    如果当前节点的数据是字符串,根据字符串的长度大小,encoding 会使用 1 字节/2字节/5字节的空间进行编码,encoding 编码的前两个 bit 表示数据的类型,后续的其他 bit 标识字符串数据的实际长度,即 data 的长度

  • entry-data:真是用于存储entry表示的数据;

在entry中存储的是int类型时,encoding和entry-data会合并在encoding中表示,此时没有entry-data字段;

typedef struct zlentry{unsigned int prevrawlensize; //表示 previous_entry_length字段的长度unsigned int prevrawlen; //表示 previous_entry_length字段存储的内容unsigned int lensize;   //表示 encoding字段的长度unsigned int len; //表示数据内容长度unsigned int headersize;//表示当前元素的首部长度,即previous_entry_length字段长度与encoding字段长度之和unsigned char encoding; //表示数据类型unsigned char *p;     //p表示当前元素首地址
}zlentry;
ziplist的缺点
  • ziplist也不预留内存空间, 并且在移除结点后, 也是立即缩容, 这代表每次写操作都会进行内存分配操作.

  • 结点如果扩容, 导致结点占用的内存增长, 并且超过254字节的话, 可能会导致链式反应: 其后一个结点的entry.prevlen需要从一字节扩容至五字节. 最坏情况下, 第一个结点的扩容, 会导致整个ziplist表中的后续所有结点的entry.prevlen字段扩容. 虽然这个内存重分配的操作依然只会发生一次, 但代码中的时间复杂度是o(N)级别, 因为链式扩容只能一步一步的计算.

    因此,压缩列表只会用于保存的节点数量不多的场景,只要节点数量足够小,即使发生连锁更新,也是能接受的。

字典-Dict

哈希表结构

typedef struct dictht {//哈希表数组dictEntry **table;//哈希表大小unsigned long size;  //哈希表大小掩码,用于计算索引值unsigned long sizemask;//该哈希表已有的节点数量unsigned long used;
} dictht

哈希节点结构

typedef struct dictEntry {//键值对中的键void *key;//键值对中的值union {void *val;uint64_t u64;int64_t s64;double d;} v;//指向下一个哈希表节点,形成链表struct dictEntry *next;
} dict

​ dictEntry 结构里键值对中的值是一个「联合体 v」定义的,因此,键值对中的值可以是一个指向实际值的指针,或者是一个无符号的 64 位整数或有符号的 64 位整数或double 类的值。这么做的好处是可以节省内存空间,因为当「值」是整数或浮点数时,就可以将值的数据内嵌在 dictEntry 结构里,无需再用一个指针指向实际的值,从而节省了内存空间。

Redis采用链式哈希存储哈希冲突

b452135f44fab77e3c578d91666e.png#pic_center)

链式哈希局限性也很明显,随着链表长度的增加,在查询这一位置上的数据的耗时就会增加,毕竟链表的查询的时间复杂度是 O(n)。

要想解决这一问题,就需要进行 rehash,也就是对哈希表的大小进行扩展。

在这里插入图片描述

rehash
  • 扩容和收缩:当哈希表保存的键值对太多或者太少时,就要通过 rehash(重新散列)来对哈希表进行相应的扩展或者收缩。具体步骤:

1、如果执行扩展操作,会基于原哈希表创建一个大小等于 ht[0].used*2n 的哈希表(也就是每次扩展都是根据原哈希表已使用的空间扩大一倍创建另一个哈希表)。相反如果执行的是收缩操作,每次收缩是根据已使用空间缩小一倍创建一个新的哈希表。

redis定义一个dict结构体,其中两个哈希表

typedef struct dict {//两个Hash表,交替使用,用于rehash操作dictht ht[2];} dict;

2、重新利用上面的哈希算法,计算索引值,然后将键值对放到新的哈希表位置上。

3、所有键值对都迁徙完毕后,释放原哈希表的内存空间。

在这里插入图片描述

触发扩容的条件

1、服务器目前没有执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且负载因子大于等于1。

2、服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令,并且负载因子大于等于5。

负载因子 = 哈希表已保存节点数量 / 哈希表大小

渐进式rehash

如果「哈希表 1 」的数据量非常大,那么在迁移至「哈希表 2 」的时候,因为会涉及大量的数据拷贝,此时可能会对 Redis 造成阻塞,无法服务其他请求。因此redis采用的是渐进式rehash

渐进式 rehash 步骤如下:

  • 给「哈希表 2」 分配空间;
  • 在 rehash 进行期间,每次哈希表元素进行新增、删除、查找或者更新操作时,Redis 除了会执行对应的操作之外,还会顺序将「哈希表 1 」中索引位置上的所有 key-value 迁移到「哈希表 2」 上;
  • 随着处理客户端发起的哈希表操作请求数量越多,最终在某个时间点会把「哈希表 1 」的所有 key-value 迁移到「哈希表 2」,从而完成 rehash 操作。

在渐进式hash过程中哈希表1不再进行增加操作,哈希表元素的删除、查找、更新等操作都会在这两个哈希表进行。

整数集-intSet

整数集合本质上是一块连续内存空间,它的结构定义如下:

typedef struct intset {//编码方式uint32_t encoding;//集合包含的元素数量uint32_t length;//保存元素的数组int8_t contents[];
} intset;

encoding 表示编码方式,的取值有三个:INTSET_ENC_INT16, INTSET_ENC_INT32, INTSET_ENC_INT64

length 代表其中存储的整数的个数

contents 指向实际存储数值的连续内存区域, 就是一个数组;整数集合的每个元素都是 contents 数组的一个数组项(item),各个项在数组中按值得大小从小到大有序排序,且数组中不包含任何重复项。(虽然 intset 结构将 contents 属性声明为 int8_t 类型的数组,但实际上 contents 数组并不保存任何 int8_t 类型的值,contents 数组的真正类型取决于 encoding 属性的值)

内存分布图

img

整数集合的升级

当在一个int16类型的整数集合中插入一个int32类型的值,整个集合的所有元素都会转换成32类型。 整个过程有三步:

  • 根据新元素的类型(比如int32),扩展整数集合底层数组的空间大小,并为新元素分配空间。
  • 将底层数组现有的所有元素都转换成与新元素相同的类型, 并将类型转换后的元素放置到正确的位上, 而且在放置元素的过程中, 需要继续维持底层数组的有序性质不变。
  • 最后改变encoding的值,length+1。

升级过程

在这里插入图片描述

那么如果我们删除掉刚加入的int32类型时,会不会做一个降级操作呢

不会。主要还是减少开销的权衡。

跳表 - ZSkipList
typedef struct zskiplistNode {//Zset 对象的元素值sds ele;//元素权重值double score;//后向指针struct zskiplistNode *backward;//节点的level数组,保存每层上的前向指针和跨度struct zskiplistLevel {struct zskiplistNode *forward;unsigned long span;} level[];
} zskiplistNode;typedef struct zskiplist {struct zskiplistNode *header, *tail;unsigned long length;int level;
} zskiplist;

跳表的结构:

在这里插入图片描述

跳表的查找过程

img

如果要查找「元素:abcd,权重:4」的节点,查找的过程是这样的:

  • 先从头节点的最高层开始,找「元素:abc,权重:3」节点,这个节点的权重比要查找节点的小,所以要访问该层上的下一个节点;
  • 但是该层的下一个节点是空节点( leve[2]指向的是空节点),于是就会跳到「元素:abc,权重:3」节点的下一层去找,也就是 leve[1];
  • 「元素:abc,权重:3」节点的 leve[1] 的下一个指针指向了「元素:abcde,权重:4」的节点,然后将其和要查找的节点比较。虽然「元素:abcde,权重:4」的节点的权重和要查找的权重相同,但是当前节点的 SDS 类型数据「大于」要查找的数据,所以会继续跳到「元素:abc,权重:3」节点的下一层去找,也就是 leve[0];
  • 「元素:abc,权重:3」节点的 leve[0] 的下一个指针指向了「元素:abcd,权重:4」的节点,该节点正是要查找的节点,查询结束。

跳表的节点层数设置:

跳表的相邻两层的节点数量的比例会影响跳表的查询性能。

那怎样才能维持相邻两层的节点数量的比例为 2 : 1 呢?

如果采用新增节点或者删除节点时,来调整跳表节点以维持比例的方法的话,会带来额外的开销。

Redis 则采用一种巧妙的方法是,跳表在创建节点的时候,随机生成每个节点的层数,并没有严格维持相邻两层的节点数量比例为 2 : 1 的情况。

具体的做法是,跳表在创建节点时候,会生成范围为[0-1]的一个随机数,如果这个随机数小于 0.25(相当于概率 25%),那么层数就增加 1 层,然后继续生成下一个随机数,直到随机数的结果大于 0.25 结束,最终确定该节点的层数

这样的做法,相当于每增加一层的概率不超过 25%,层数越高,概率越低,层高最大限制是 64。

虽然我前面讲解跳表的时候,图中的跳表的「头节点」都是 3 层高,但是其实如果层高最大限制是 64,那么在创建跳表「头节点」的时候,就会直接创建 64 层高的头节点

如下代码,创建跳表时,头节点的 level 数组有 ZSKIPLIST_MAXLEVEL个元素(层),节点不存储任何 member 和 score 值,level 数组元素的 forward 都指向NULL, span值都为0。

/* Create a new skiplist. */
zskiplist *zslCreate(void) {int j;zskiplist *zsl;zsl = zmalloc(sizeof(*zsl));zsl->level = 1;zsl->length = 0;zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {zsl->header->level[j].forward = NULL;zsl->header->level[j].span = 0;}zsl->header->backward = NULL;zsl->tail = NULL;return zsl;
}

其中,ZSKIPLIST_MAXLEVEL 定义的是最高的层数,Redis 7.0 定义为 32,Redis 5.0 定义为 64,Redis 3.0 定义为 32。

快表-quicklist
typedef struct quicklist {//quicklist的链表头quicklistNode *head;      //quicklist的链表头//quicklist的链表尾quicklistNode *tail; //所有压缩列表中的总元素个数unsigned long count;//quicklistNodes的个数unsigned long len;       ...
} quicklist;typedef struct quicklistNode {//前一个quicklistNodestruct quicklistNode *prev;     //前一个quicklistNode//下一个quicklistNodestruct quicklistNode *next;     //后一个quicklistNode//quicklistNode指向的压缩列表unsigned char *zl;              //压缩列表的的字节大小unsigned int sz;                //压缩列表的元素个数unsigned int count : 16;        //ziplist中的元素个数 ....
} quicklistNode;

quicklist内存布局图:

img

在向 quicklist 添加一个元素的时候,不会像普通的链表那样,直接新建一个链表节点。而是会检查插入位置的压缩列表是否能容纳该元素,如果能容纳就直接保存到 quicklistNode 结构里的压缩列表,如果不能容纳,才会新建一个新的 quicklistNode 结构。

quicklist 会控制 quicklistNode 结构里的压缩列表的大小或者元素个数,来规避潜在的连锁更新的风险,但是这并没有完全解决连锁更新的问题

listpack

在这里插入图片描述

  • encoding,定义该元素的编码类型,会对不同长度的整数和字符串进行编码;
  • data,实际存放的数据;
  • len,encoding+data的总长度;

listpack如何解决反向遍历的?

首先访问 listpack 的前4字节得到总长度,然后就可以定位到末尾结尾符位置。然后指针左移就可以访问到最后一个元素的长度 len,指针再左移 len 就可以访问最后一个元素的 encoding,根据编码方式访问元素。指针再左移又可以访问到倒数第2个元素的长度,以此类推。
访问元素长度len字段时,有一个关键点,就是如何判断 len 部分结束了。因为 len 可能占用1字节,也可能占用多个字节。listpack 的做法是,每个字节只使用 7 Bit,最高位来表示是否还要继续读

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

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

相关文章

DP10RF001一款200MHz~960MHz 低功耗(G)FSK/OOK无线收发芯片应用无线遥控工控设备无线门禁传感器等

产品概述 DP10RF001是一款工作于200MHz~960MHz范围内的低功耗、高性能、单片集成的(G)FSK/OOK无线收发机芯片。内部集成完整的射频接收机、射频发射机、频率综合器、调制解调器&#xff0c;只需配备简单、低成本的外围器件就可以获得良好的收发性能。芯片支持灵活可设的数据包…

Jmeter从数据为查找结果集数据方法随笔

一、Jmeter连接数据库 1.下载对应数据库的驱动包到jmeter安装目录的lib下ext文件中&#xff0c;并导入到jmeter的测试计划中&#xff0c;本实例中使用的是mysql如下所示&#xff1a; 点击测试计划–>点击浏览–>选中mysql驱动jar包–>打开 2.添加线程组&#xff0c;…

毕设(三)——nb-lot与onenet通信

文章目录 一、前言二、nb-lot与onenet的连接2.1 创建产品2.2 创建设备2.3 连接2.4 发送数据 三、onenet的数据可视化 刚刚看了一个关于nb-lot的视频&#xff0c;我看到up是用onenet原生的GUI就能做到数据的显示&#xff0c;十分亮眼 主要是它能把地图也一起显示出来&#xff0c…

2024mathorcup数学建模思路教学

大家好呀&#xff0c;认证杯数学建模开始了&#xff0c;来说一下选题建议以及思路吧&#xff1a; 首先定下主基调&#xff0c; 本次mathorcup数学应用挑战赛推荐大家选择C题&#xff0c;难度方面&#xff1a;A≈B&#xff1e;D&#xff1e;C。 我们预计4.13日晚上前更新完毕…

Stable Diffusion 本地部署教程:详细步骤与常见问题解析

作为一位热衷于探索前沿AI技术的博主&#xff0c;近期我深度研究了Stable Diffusion模型的本地部署过程。在这篇教程中&#xff0c;我将详述从环境准备到模型运行的每个步骤&#xff0c;并针对常见的部署问题给出解决方案&#xff0c;帮助你顺利在本地开启Stable Diffusion的创…

【图像处理】-小议YUV色彩空间-YUV和RGB格式的来由,相互关系以及转换方式,并对编程实现的YUV转为RGB程序进行介绍

小议YUV色彩空间 摘要: 在视频图像处理等相关相关领域&#xff0c;YUV是一个经常出现的格式。本文主要以图解的资料形式详细描述YUV和RGB格式的来由&#xff0c;相互关系以及转换方式&#xff0c;并对编程实现的YUV转为RGB程序进行介绍。 1 引言 自然界的颜色千变万化&#xff…

【前缀合】Leetcode 和可被 K 整除的子数组

题目解析 974. 和可被 K 整除的子数组 算法讲解 前置知识点&#xff1a; 同余定理&#xff1a;(ab) % c 0 可以得出 a % c b % cC中负数求余的结果是负数&#xff0c;但是本题需要的是正数&#xff0c;所以我们为了修正这个结果&#xff0c;需要进行(负数 % 正数 正数) %…

无法用raven-js,如何直接使用TraceKit标准化错误字符串(一次有趣的探索)

引子&#xff1a;网上三年前&#xff08;2020&#xff09;的文章介绍了一个raven-js 简单说就是把堆栈信息格式化兼容各浏览器&#xff0c;便于查看错误来源。 **but&#xff1a;**到处找了一下raven-js&#xff0c;已经没有官方出处了&#xff0c;只在Sentry的源码仓库里发现…

【数据结构】单链表(二)

目录 1.查找数据 2.指定位置插入和删除节点 2.1 指定位置之前插入节点 2.2 指定位置之后插入节点 2.3 删除指定位置节点 2.4 删除指定位置之后的节点 3.销毁链表 我们接着上一篇【数据结构】单链表&#xff08;一&#xff09;-CSDN博客 来继续实现单链表 1.查找数据 在…

2. 如何让mybatis-plus的逻辑删除注解@TableLogic临时失效

文章目录 如何让mybatis-plus的逻辑删除注解TableLogic临时失效1. 场景复现1.1 controller代码1.2 service层代码1.3 entity代码 2. 问题分析3. 解决方案3.1 说明3.2 核心代码3.3 service方法对应修改为3.4 运行结果 如何让mybatis-plus的逻辑删除注解TableLogic临时失效 1. 场…

【QT教程】QT6 QML界面布局艺术

QT6 QML界面布局艺术 使用AI技术辅助生成 QT界面美化视频课程 QT性能优化视频课程 QT原理与源码分析视频课程 QT QML C扩展开发视频课程 免费QT视频课程 您可以看免费1000个QT技术视频 免费QT视频课程 QT统计图和QT数据可视化视频免费看 免费QT视频课程 QT性能优化视频免费看…

7、configMap

1、configMap是什么 类似与pod的配置中心&#xff0c;不会因为pod的创建销毁&#xff0c;相关配置发生改变 pod定义硬编码意味着需要有效区分⽣产环境与开发过程中的pod 定义。为了能在多个环境下复⽤pod的定义&#xff0c;需要将配置从pod定义描 述中解耦出来。 2、向容器中…

Mongodb入门--头歌实验MongoDB 文档的高级查询操作

数据库存储了大量的数据&#xff0c;当我们需要特定的数据时就要使用查询方法&#xff0c;根据一定的条件&#xff0c;筛选出我们想要的数据&#xff0c;前一章我们简单介绍了条件操作符&#xff08;<、<、>、>、!等&#xff09;&#xff0c;在这一章中我们将更全面…

重生奇迹MU弓箭手装备介绍

重生奇迹MU弓箭手装备搭配什么好呢&#xff1f;装备的搭配对于角色来说帮助很大&#xff0c;所以我们更加要选择合适的装备来搭配。 装备打造 ①装备强化&#xff1a;装备强化需要强化材料 灵魂宝石、祝福宝石、玛雅宝石&#xff0c;强化有几率失败&#xff0c;失败不会导致…

贪心算法|968.监控二叉树

力扣题目链接 class Solution { private:int result;int traversal(TreeNode* cur) {// 空节点&#xff0c;该节点有覆盖if (cur NULL) return 2;int left traversal(cur->left); // 左int right traversal(cur->right); // 右// 情况1// 左右节点都有覆盖if (le…

MM-Grounding-DINO的训练推理(待更新)

1、简单介绍 继前面发布的 GroundingDino 和 Open-GroundingDino的推理 和 Open-GroundingDino的训练实现&#xff0c;作为 GroundingDino延续性的文本检测网络 MM-Grounding-DINO 也发布了较详细的 训练和推理实现教程&#xff0c;而且操作性很强。作为学习内容&#xff0c;也…

IO流【内存流、打印流、随机访问流】;初识网络编程

day37 IO流 继day36 各种流 对象流 day36 内存流 class ByteArrayInputStream – 内存输入流 class ByteArrayOutputStream – 内存输出流 注意&#xff1a; 内存流是程序和内存交互&#xff0c;跟文件无关内存流是程序到内存的通道&#xff0c;是关闭不掉的 应用场景&#x…

深度学习pytorch好用网站分享

深度学习在线实验室Featurizehttps://featurize.cn/而且这个网站里面还有一些学习教程 免费好用 如何使用 PyTorch 进行图像分类https://featurize.cn/notebooks/5a36fa40-490e-4664-bf98-aa5ad7b2fc2f 华为modelArtshttps://bbs.huaweicloud.com/forum/thread-76328-1-1.html…

阿里云飞燕平台搭建与linux程序通信(全图文教程)

阿里云飞燕平台搭建与linux程序通信&#xff08;全图文教程&#xff09; 一、MQTT理论1.1 概念1.2 理解发布和订阅1.3 MQTT传输的消息组成 二、阿里云飞燕平台搭建2.1 产品创建2.2 自定义自己的功能2.3 人机交互 三、测试3.1代码分析3.2 效果验证 一、MQTT理论 1.1 概念 MQTT…

flutter组件_AlertDialog

官方说明&#xff1a;A Material Design alert dialog. 翻译&#xff1a;一个材料设计警告对话框。 作者释义&#xff1a;显示弹窗&#xff0c;类似于element ui中的Dialog组件。 AlertDialog的定义 const AlertDialog({super.key,this.icon,this.iconPadding,this.iconColor,t…