【2024最新版,redis7】redis底层的10种数据结构

前言:本文redis版本:7.2.4
本文语雀原文地址(首发更新):https://www.yuque.com/wzzz/redis/xg2cp37kx1s4726y
本文CSDN转载地址: https://blog.csdn.net/u013625306/article/details/136842107

1. 常见的数据结构

Redis常见的数据结构有5种: String,List, Set, ZSet, Hash,这只是对外的数据结构
Redis对内的底层数据结构具体实现方法还有如下几种:int, raw, embstr, linkedlist, ziplist, hashtable, intset, skiplist,quicklist,listpack
他们之间的映射关系如下
在这里插入图片描述

2. Redis对象基础知识

Redis对象,就是不管你是什么类型的redis变量,都必须要有这个对象,可以想象是一种对象头,就比如你java中所有的类,不管是系统的,新建的,他们都有一个Object父类
Redis对象的定义如下:

typedef struct redisobject{
    // 类型 4bit 即用于表示[String, List, Set, ZSet, Hash, Geo, HyperLogLog, Stream, Bitmaps]中的一种
    unsigned type:4;
    // 编码方式 4bit 即用于表示[int, embstr, raw, ziplist...]中的一种
    unsigned encoding:4;
    // LRU时间 24bit (相对于server.lrulock)
    unsigned lru:24;
    // 引用计数 32bit redis里面的数据可以通过引用计数进行共享
    int refcount:32;
    // 用于指向具体对象的指针 64bit,比如指向string, list等等
    void *ptr;
}

一个redisobject占用的存储空间:4+4+24+32+64=128bit/8=16字节

3. String底层数据结构

3.1 int类型

首先 object encoding key这个命令可以返回key在redis中内部的编码方式
当保存的字符串是数字类型时,长度≤19位,此时内部就会用int表示
image.pngimage.png
当保存的是个20位数字时
在这里插入图片描述

image.png
当保存的字符串首位数字是0时,不会用int表示,因为int前面的0存可以存,但读不出来呀
image.pngimage.png

3.2 embstr类型

embstr类型是redis专门用来保存短字符串的一种优化编码,其结构如下图所示
embstr底层编码方式
该结构中,redisobject是必须有的,每个redis对象都有。embstr使用时,只分配一次内存空间,因为redisobject和sdshdr是连续的,所以在一起分配,当embstr字符串扩大时,也就照成了要重新给embstr类型的字符串重新分配内存空间,所以redis中为了杜绝这种情况,embstr字符串是只读的,不能修改,如果修改了,就会变为raw编码方式,无论其长度是否达到了44字节

3.3 raw类型

raw类型是redis用来保存,可变长的,可修改的字符串,其结构如下图所示:
raw编码的字符串对象
如图所示,raw方式保存字符串时,会进行两次申请内存空间,第一次是给redisobject申请,第二次是给sdshdr申请。
这样的方式,虽然比embstr多申请一次内存空间,但是这种方式当字符串长度发生扩展时,只用重新申请sdshdr的内存就行了,然后修改ptr指针,可以有效防止内存碎片。

3.4 SDS 简单动态字符串

3.4.1 3.2版本之前

SDS的结构定义在sds.h文件中,以前版本的SDS比较简单,有三个属性,分别是len, free, buf数组

// 3.0
struct sdshdr {
    // 记录buf数组中已使用字节的数量,即SDS所保存字符串的长度
    unsigned int len;
    // 记录buf数据中未使用的字节数量
    unsigned int free;
    // 字节数组,用于保存字符串
    char buf[];
};

3.4.2 3.2版本及之后

在3.2版本之后,根据字符串长度的不同,分别初始化不同的sds

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

3.2版本之后,会根据字符串的长度来选择对应的数据结构

static inline char sdsReqType(size_t string_size) {
    if (string_size < 1<<5)  // 32
        return SDS_TYPE_5;
    if (string_size < 1<<8)  // 256
        return SDS_TYPE_8;
    if (string_size < 1<<16)   // 65536 64k
        return SDS_TYPE_16;
    if (string_size < 1<<32)  // 4294967296 4G
        return SDS_TYPE_32;
    return SDS_TYPE_64;
}

以下是raw编码方式下sdshdr8的一个字符串示例:

3.5 SDS动态字符串与C语言字符串的区别

C语言使用长度N+1的字符数组来表示长度为N的字符串,字符数组的最后一位是’\0’, 用以表示字符串的结尾,但是这种方式不能满足redis对字符串的安全性,效率和功能上的要求,所以redis在字符串上面使用了sds动态字符串来保存字符串,相比与c语言字符串,sds动态字符串有以下优点

C语言字符串SDS动态字符串SDS的优点
C语言中没有保存字符串长度,当想获取字符串长度时,需要遍历字符串,统计长度,时间复杂度O(n)SDS结构中保存了字符串的长度len, 当程序想获取字符串的长度时,直接访问len属性即可,时间复杂度为O(1),确保了获取字符串长度不会成为redis的性能瓶颈常数复杂度获取字符串长度
  • C语言中如果对字符串进行操作,如果是增长字符串,会导致内存的重新分配。
  • 如果是截取字符串,未截取的地方需要被释放掉,如果没有释放,就会导致内存溢出
    | SDS结构中,对字符串数组采用预申请,len属性记录已经使用的长度,alloc属性记录所有的长度(在3.0版本使用free记录未使用空间,3.2版本则改为alloc记录总长度),sds采用这种空间预分配惰性空间释放两种策略,解决了字符串拼接和截取两种场景下的空间问题。
  • 空间预分配:当对一个SDS字符串进行增长操作且内存不够用时,SDS会进行内存的申请,申请时,如果len≤1MB,那么就申请len+len+1的内存,也就是额外申请已使用内存len的两倍+1。如果申请时len>1MB, 那么就申请len+1MB+1的内存,每次扩增1MB。
  • 惰性空间释放:用于优化SDS字符串缩短操作,当SDS的字符串进行缩短时,程序不会释放其内存,而是使用free字段记录下还有多少未使用的空间,等待以后使用
    | 杜绝缓冲区溢出
    减少修改字符串带来的重新分配内存的次数 |
    | 可以使用所有的<string.h>中的函数 | 可以使用部分<string.h>中的函数 | |
    | 只能保存文本数据 | 可以保存文本或二进制数据 | |

4. List底层数据结构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.1 ziplist类型(压缩列表)

在说Redis的ziplist之前,先看一下什么是压缩列表。压缩列表是一种紧凑型的数据结构,目的就是为了节省内存空间,他借鉴了数组的存储思想,占用一块连续的内存空间,但又与数组有些许区别。因为数组要求每个元素占用相同的存储空间,且每个存储空间的大小依据最大的那个元素来计算。

上面图中所表示,前4个元素都浪费了存储空间,所以为了保证占用内存空间的连续性和节省更多的内存空间,可以对数组进行压缩,使每个元素的大小为实际存储的大小。但这样的话,不知道每个元素具体的大小了,代码不能判断下一个元素的起点,所以还要为每个元素增加一个字段用于记录每个元素占据的位置大小,用于标识当前元素的大小,这样代码在遍历的时候,就能判断下一个元素的起点,这就构成了最简单的压缩链表
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
redis的ziplist就是基于上面的压缩列表做了自己的封装,接下来我们看redis中ziplist具体的实际结构如何:

ziplist最大的好处就是节省了内存空间,但他的缺点也很多。

  • 不能存储太多的元素,否则遍历效率极低
  • 当新增或修改某个元素时,会重新分配内存,甚至会引起连锁更新的问题。

**连锁更新:**连锁更新就是我只更新了一个元素,但由于某个原因,导致所有元素都进行了内存重分配,导致所有元素都进行了更新,就会降低效率。
那么ziplist如何出现连锁更新问题的呢?这就要看entry节点中prevlen属性占用空间大小了,prevlen属性记录是上一个节点的大小,当上一个节点大小小于256字节时,prevlen属性需要1字节的空间来保存这个长度大小,当上一个节点大小超过256字节时,prevlen属性就需要5字节的空间来保存这个长度大小。如果当上一个节点增大超过256字节时,就会引发下一个节点的prevlen属性内存空间增大,需要为下一个节点重新分配额外的4字节空间。循环往复,有可能每一个下个节点都被导致重新分配内存空间,直至最后一个节点,就会导致连锁更新。

因此:ziplist只适合保存结点数量不多的场景。

4.2 linkedlist类型(双向链表)

redis中的linkedlist就是基于双向链表实现的。以下是redis中linkedlist具体结构图:

可以看到linkedlist中包含了3个属性:

  • head:用于指向链表中的第一个结点
  • tail:用于指向链表中的最后一个结点
  • len:用于记录链表中所有结点的数量

除此之外,还包含了3个函数:

  • dup:结点复制函数,用于复制节点
  • free:结点释放函数,用于释放节点的内存空间
  • match:结点比较函数,用于比较节点之间的值

linkedlist的优缺点:

linkedlist的优点linkedlist的缺点
  • 可以直接获取到头结点和尾结点,时间复杂度O(1)
  • 每个结点中都有next和prev属性,可以方便的获取下一个结点和上一个结点,时间复杂度也是O(1)
  • linkedlist中维护了链表的长度len,可以直接获取链表长度,时间复杂度也是O(1)
    |
  • 双向链表中的每个结点内存空间都是不连续的,无法很好的利用cpu缓存
  • 双向链表遍历的时间复杂度为O(n)
    |

4.3 quicklist类型

由于ziplist和linkedlist都存在着不可避免的缺陷,所以在redis3.2版本之后,引入了一种新的数据结构:QuickList(快速列表)。List对象的底层数据结构,也由ziplist和linkedlist变为QuickList。
QuickList结合了ziplist和linkedlist的优点,它是一个压缩列表ziplist为结点的双向链表,以下是它的数据结构图:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
可以看到quicklist的数据结构和linkedlist总体上比较相似,都是包含了一个双向链表,并且都维护了双向链表的head结点和tail结点,以及结点的数量,最大的区别就是在双向链表中存储的value值发生了变化,linkedlist中存储的值是实际的值,而quicklist中存储的值是一个指向ziplist的指针。该ziplist的最大大小由quicklist的fill属性进行限制,当某个节点的ziplist大小超出了该限制,就会在链表中新建一个ziplist保存到新的quicklist结点中。

4.4 listpack类型(紧凑列表)

5. Set底层数据结构

5.1 intset

intset适用于保存都是整数的集合,根据编码的不同可以保存不同大小范围的整数,如果三种编码都无法保存这个整数,或者有元素不是整数,就会升级为hashtable的编码方式进行编码。

5.2 hashtable

HashTable(哈希表)是一个保存key-value键值对的结构体,它与Java中的HashMap相似,每个key都是唯一的,可以通过key去查询和修改其value值。
在redis中,hashtable底层实现是基于数组的,数组的每个元素相当于一个bucket桶,当通过哈希散列函数计算出key对应的hash值(也就是数组下标)时,就会将该元素放入在数组中对应的下标位置,当发生hash冲突时,就会采用链地址法,在对应的bucket桶下形成一个链表来保存这些数据。

常见的解决hash冲突的方式还有以下几种:

  1. 链地址法:形成一个Entry链表来保存数据
  2. 二次hash:用另一种函数重新计算hash值,尝试映射到其他的bucket中
  3. 公共溢出区:将产生Hash冲突的元素统一存放到一个公共的区域
  4. 开放定址法:冲突的Entry按照一定的规则(线性探测、平方探测)在hash表中找到下一个空闲的bucket存放

下面是hashtable的结构图:

从结构中可以看到,映射到相同数组下标位置的元素,会形成链表,当redis再次查询时,会先计算数组下标位置,然后再沿着链表进行遍历查找,时间复杂度为O(1)+O(n)=O(n).
哈希表使用了链式哈希法虽然很好的解决了哈希冲突的问题,但随着结点越来越多,链表上的结点元素也越来越多,就会照成查询时的成本变高,会大大降低查询效率。
redis为了解决hash冲突,在数据量达到一定阈值的时候,就会对hash表进行rehash(重新计算hash)操作,会将数组进行扩容,对每个元素重新计算hash,然后重新映射到新位置。
触发rehash的条件有两个:

  1. 负载因子≥1,并且服务器没有执行bgsave命令或者bgrewriteaof命令,就会执行rehash操作,但并一定100%执行
  2. 负载因子≥5,说明此时的hash表冲突已经很严重了,将强制执行rehash操作

负载因子 = hash表中entry结点的总数量 / 数组大小

redis为了解决hash冲突,在进行rehash的时候,也设计了一种dict结构,dict中包含了两个dictht哈希表ht[0]和ht[1],默认情况下,这两个哈希表只使用一个。以下是dict的结构:

当我们第一次新增数据的时候,元素结点会存放在ht[0]中,当ht[0]中的结点负载因子超过阈值时,就会重新计算结点的hash值,将其放入到ht[1]中。
rehash步骤:

  1. 给哈希表ht[1]分配空间,分配空间的大小为 2n中第一个>ht[0].used * 2的n值。比如上图中ht[0].used * 2=22, 而25>22>24,所以n=5,即分配的空间大小为32.
  2. 将ht[0]中的entry结点经过rehash重新运算后,迁移到ht[1]中
  3. 迁移完成后,释放ht[0]的内存空间,并把ht[1]设置为ht[0]
  4. 创建一个新的ht[1],为下次扩容做准备

通过扩容操作,并进行rehash操作,可以有效地解决hash冲突的问题,但当hash表中的元素很大时,就会导致rehash动作耗时很长,而redis是单线程的,一旦耗时过长影响到后续的操作,那就是得不偿失的。因此在redis中没有采用直接全部rehash,而是采用渐进式rehash操作来进行。
**渐进式rehash:**不一次性把所有的数据rehash到新的哈希表中,而是在每次对哈希表的元素进行增删查改的时候,顺带把某个数组下标的链表上所有的结点进行rehash操作,直至把所有的元素rehash。在新增数据时,对于新增的数据将其放入ht[1]中,对于查询数据时,先去查询ht[0],再去查询ht[1]。在渐进式rehash操作时,两个dict中都会存储数据,但ht[0]中的数据会越来越少,直至全部rehash进入到ht[1]中。
以下是rehash的结构图:

6. ZSet底层数据结构

6.1 ziplist类型

ziplist的具体结构在4.1节已经介绍过了。但ZSet中具体的做法是将元素和其对应的分值分别保存到两个相邻的entry的content中。
当ziplist作为zset的底层数据结构时,每个集合元素使用两个紧挨在一起的压缩列表结点来保存。第一个结点保存元素成员,第二个结点保存结点的分值。
image.png

redis选择ziplist作为zset的底层数据结构也是有限制的:

  1. 有序集合中保存的元素数量<128个
  2. 有序集合保存的所有元素的长度小于64字节

否则就会使用skiplist作为zset的底层实现。

6.2 skiplist类型

ziplist俗称跳表,跳表是一个基于有序链表实现的可以快速查找元素的数据结构。下面是一个普通的有序链表的结构图:

该链表中如果想找到靠后的结点,就需要挨个遍历结点,时间复杂度为O(n)。那么有没有办法提高查询效率呢,当然是有的。既然我们的链表是有序的,我们可不可以跳过前面的结点呢?这时候我们的初级链表就出来了(一个含有两层链表的跳表, 相当于带了一层索引):

这样我们查询12的话:

  1. 只有一层链表:1->3->6->8->9->12,需要遍历6次,时间复杂度O(n)
  2. 有两层链表:1->6->9->12,需要遍历4次,时间复杂度O( n 2 \frac{n}{2} 2n)
  3. 同理,对于一个n层链表来说,时间复杂度为O( l o g n logn logn)

在上面的结构中,跳跃表遵守上一层结点是下一层结点的一半,但是在发生大量插入元素的时候,就会发生频繁调整跳跃表这个操作。
所以在redis中设计了一种升层算法,对于每一个新增的结点,初始化其层数为1,然后循环以下算法步骤:算法算出一个0~1之间随机数,如果随机数<0.25,则层数增加一层,然后循环这个步骤,直到生成的随机数> 1 4 \frac{1}{4} 41,如果循环到第三次退出循环时,那么这个新增节点的层数就是2。这个算法对于每个结点来说,新增一层的概率是0.25,层数越高,概率越小。对于新增的结点,只需要修改其前向指正和后向指针即可,其它结点不受影响。
综上所述,通过将有序集合的部分节点层,从最上层结点开始依次查找,如果本层的next结点大于我们想要找的结点,那么就自降一层再依次查找,如果找到了就返回其值,找不到就返回null。如果跳跃表的层数很多的话,那么就会跳过很多节点,从而提升查询的效率,这就是跳跃表的思想。
跳跃表有以下性质:

  1. 跳跃表有很多层结构组成,最底层的结点个数为跳跃表的长度
  2. 跳跃表有一个头结点,头结点中默认有一个32层的结构(redis默认层数最高32层),每层的结构都包含一个指向本层下一个元素的next指针
  3. 除了头结点外,有最高层的结点的层数称为跳跃表的高度
  4. 每层都是一个有序链表,随着数据score依次递增
  5. 除了头结点外,最底层的链表包含所有的元素

在跳跃表中,对于一个新增的元素来说,其新增结构如下:

7. Hash底层数据结构

7.1 ziplist类型

当元素较少时,使用ziplist保存hash结构的元素,当元素个数超过一定的限制后,使用hashtable来保存。

7.2 hashtable类型

hashtable在上述5.2章节中已经介绍。

7.3 listpack类型

本文参考文献:

  • https://blog.csdn.net/YTREE_BJ/article/details/119415521
  • https://zhuanlan.zhihu.com/p/619828101
  • https://www.jianshu.com/p/674c9635b2b9
  • https://baijiahao.baidu.com/s?id=1731524214636496899&wfr=spider&for=pc
  • https://www.jianshu.com/p/674c9635b2b9
  • https://blog.51cto.com/u_39029/6501913
  • https://blog.csdn.net/u013277209/article/details/125998869
  • https://blog.csdn.net/qq_32099833/article/details/133889188

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

.htaccess全站设置SSL,wordpress全站设置SSL,网站重定向的次数过多”错误最佳解决方法教程

.htaccess全站设置SSL,wordpress全站设置SSL&#xff0c;网站重定向的次数过多”错误最佳解决方法教程 网上找了很多教程网无效**.htacces**设置&#xff0c;访问后台出现重定向次数过多&#xff0c;导致无法访问 找了好久&#xff0c;测试用AI机器人无法解决&#xff0c;参考…

【Linux】详谈进程优先级进程调度与切换

一、进程优先级 1.1、为什么要有优先级 进程要访问某种资源&#xff0c;进程通过一定的方式排队&#xff0c;确认享受资源的优先顺序。计算机中资源过少&#xff0c;所以进程访问某种资源时需要排队。 1.2、优先级的具体表示 进程的优先级其实就是PCB中的一个整形变量…

python失物招领系统-安卓-flask-django-nodejs-php

对于本失物招领 的设计来说&#xff0c; 它是应用mysql数据库、安卓等技术动态编程以及数据库进行努力学习和大量实践&#xff0c;并运用到了 建设中在整个系统的设计当中&#xff0c;具体根据网上失物招领的现状来进行开发的&#xff0c;具体根据用户需求实现网上失物招领网络…

产品推荐 | 基于XC7K325T的FMC接口万兆光纤网络验证平台

01、产品概述 TES307是一款基于XC7K325T FPGA的万兆光纤网络验证平台&#xff0c;板卡具有1个FMC&#xff08;HPC&#xff09;接口&#xff0c;4路SFP万兆光纤接口、4路SATA接口、1路USB3.0接口。 板载高性能的FPGA处理器可以实现光纤协议、SATA总线控制器、以及USB3.0高速串…

【Node.js从基础到高级运用】十五、单元测试与集成测试

引言 在Node.js开发过程中&#xff0c;测试是确保代码质量和功能正确性的关键步骤。单元测试和集成测试是最常见的测试类型。下面我们将使用Jest框架来进行测试。 单元测试 单元测试是指对软件中的最小可测试单元进行检查和验证。在Node.js中&#xff0c;这通常指的是函数或者…

ISIS接口明文认证实验简述

默认情况下&#xff0c;ISIS接口认证通过在ISIS协议数据单元&#xff08;PDU&#xff09;中添加认证字段&#xff0c;例如&#xff1a;一个密钥或密码&#xff0c;用于验证发送方的身份。 ISIS接口认证防止未经授权的设备加入到网络中&#xff0c;并确保邻居之间的通信是可信的…

智慧城市:提升城市治理能力的关键

目录 一、智慧城市的概念及特点 二、智慧城市在提升城市治理能力中的应用实践 1、智慧交通&#xff1a;提高交通治理效率 2、智慧政务&#xff1a;提升政府服务水平 3、智慧环保&#xff1a;加强环境监测与治理 4、智慧安防&#xff1a;提高城市安全水平 三、智慧城市在…

【计算机视觉】Gaussian Splatting源码解读补充

本文旨在补充gwpscut创作的博文学习笔记之——3D Gaussian Splatting源码解读。 Gaussian Splatting Github地址&#xff1a;https://github.com/graphdeco-inria/gaussian-splatting 论文地址&#xff1a;https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/3d_gauss…

基于nodejs+vue班级管理系统的设计与实现-flask-django-python-php

随着电子技术的普及和快速发展&#xff0c;线上管理系统被广泛的使用&#xff0c;有很多事业单位和商业机构都在实现电子信息化管理&#xff0c;班级管理系统也不例外&#xff0c;由比较传统的人工管理转向了电子化、信息化、系统化的管理。随着互联网技术的高速发展&#xff0…

STM32编写ADC功能,实现单路测量电压值(OLED显示)

先来看看本次实验的结果吧&#xff1a;stm32点电压测量范围为0-3.3V&#xff0c;数值为&#xff1a;0-4095 来看看这个工程的文件布局吧&#xff1a; 实现ADC功能总共分为六步&#xff1a; 第一步&#xff1a;开始RCC时钟&#xff0c;包括ADC和GPIO的时钟&#xff0c;ADCCLK的…

六、C#快速排序算法

简介 快速排序是一种常用的排序算法&#xff0c;它基于分治的思想&#xff0c;通过将一个无序的序列分割成两个子序列&#xff0c;并递归地对子序列进行排序&#xff0c;最终完成整个序列的排序。 其基本思路如下&#xff1a; 选择数组中的一个元素作为基准&#xff08;pivot…

SQL server服务连接失败,通过端口1433连接到主机 localhost的 TCP/IP 连接失败

SQL server服务连接失败&#xff0c;通过端口1433连接到主机 localhost的 TCP/IP 连接失败 出现这个错误的时候&#xff0c;首先确保sql的服务正常启动 通常来说正常安装的SQL server之后&#xff0c;会自带一个软件 打开&#xff1a;SQL server配置管理器 确认一下红框内的…

GitHub Copilot+ESP开发实战-串口

上篇文章讲了GitHub Copilot在应用中可能遇到的问题&#xff0c;接下来小启就简单介绍下GitHub Copilot在ESP32开发中C语言实现串口功能&#xff0c;感兴趣的可以看看。 一、向Copilot提问&#xff1a; 1. ESP32用C语言实现串口初始化&#xff1b; 2.配置uart为1&#xff0c…

【STM32嵌入式系统设计与开发】——6矩阵按键应用(4x4)

这里写目录标题 一、任务描述二、任务实施1、SingleKey工程文件夹创建2、函数编辑&#xff08;1&#xff09;主函数编辑&#xff08;2&#xff09;LED IO初始化函数(LED_Init())&#xff08;3&#xff09;开发板矩阵键盘IO初始化&#xff08;ExpKeyBordInit()&#xff09;&…

QT配置libtorch(一步到位!!!防止踩坑)

QT配置libtorch Qt下载QT配置MSVCQT配置Libtorch Qt下载 Qt点击下载 Qt的安装选择MSVC2017 64-bit(一定要安装&#xff0c;这关乎后面的配置&#xff01;&#xff01;&#xff01;)&#xff0c;其他的根据自己的选择进行安装 QT配置MSVC Visual Studio点击安装 这里需要安装VS以…

Flutter-实现扫描线移动效果

效果 唠叨 在许多应用中&#xff0c;我们经常会看到扫描线的动画效果&#xff0c;比如二维码扫描、条形码扫描等。在Flutter中&#xff0c;我们可以通过自定义控件来实现这种扫描线移动的效果。本文将介绍如何使用Flutter创建一个扫描线移动的控件&#xff0c;并分析其实现思路…

HarmonyOS NEXT应用开发之Navigation实现多设备适配案例

介绍 在应用开发时&#xff0c;一个应用需要适配多终端的设备&#xff0c;使用Navigation的mode属性来实现一套代码&#xff0c;多终端适配。 效果图预览 使用说明 将程序运行在折叠屏手机或者平板上观看适配效果。 实现思路 本例涉及的关键特性和实现方案如下&#xff1a…

学习总结1

算法 这两天对搜索(主要是dfs)进行了复习,写了四道题目. 解题思路 这道题我用dfs进行解题,这道题比起其他的只多了一个Z轴也就是多了两个方向. 代码 #include <string.h> #include <stdio.h> char g[31][31][31]; int ne[7][3]{{1,0,0},{-1,0,0},{0,1,0},{0,-1…

React状态管理库快速上手-Redux(一)

基本使用 安装 pnpm install reduxjs/toolkit react-redux创建一个仓库 定义state createSlice相当于创建了一个模块仓库&#xff0c;initialState存放状态&#xff0c;reducers存放改变状态的方法。 import { createSlice } from reduxjs/toolkitexport const counterSli…

2024.3.19

思维导图 模拟面试 1.友元的作用 答&#xff1a;通过关键字friend&#xff0c;可以让一些函数或者类&#xff0c;可以访问一个类中的私有数据成员。 2.匿名对象的作用 答&#xff1a;匿名对象就是没有名字的对象&#xff0c;是用来给有名对象进行初始化工作的。 3.常成员函…
最新文章