当前位置: 首页 > news >正文

验证go循环删除slice,map的操作和map delete操作不会释放底层内存的问题

目录

  • 切片 for 循环删除切片元素
    • 其他循环中删除slice元素的方法
      • 方法1
      • 方法2(推荐)
      • 方法3
    • 官方提供的方法
    • 结论
  • 切片 for 循环删除map元素
    • goalng map delete操作不会释放底层内存
    • go map原理
      • 源码
      • CRUD
        • 查询
        • 新增
      • 操作注意事项
        • map元素是无法取址的
        • map是线程不安全的

切片 for 循环删除切片元素

在 Go 语言中,使用 for 循环删除切片元素可能会引发意外的结果,因为切片的长度在循环过程中可能会发生变化,导致索引越界或不正确的元素被删除。这是因为在删除切片元素时,删除操作会影响切片的长度和索引,从而影响后续的迭代。

以下是一个示例,演示了在循环中删除切片元素可能引发的问题:

package mainimport ("fmt"
)func main() {// 8*5 =40slice := []int{1, 2, 2, 2, 2, 4, 5}fmt.Printf("切片长度:%d,容量:%d \n", len(slice), cap(slice))for index, value := range slice {if value == 2 {slice = append(slice[:index], slice[index+1:]...)fmt.Println("删除了一次2")}fmt.Println(index, value)}fmt.Println(slice)fmt.Printf("切片长度:%d,容量:%d \n", len(slice), cap(slice))slice = slice[:cap(slice)]fmt.Println(slice)
}

在这里插入图片描述

在这个示例中,删除切片 slice 中值为 2 的元素。然而,由于删除操作改变了切片的长度和索引,循环会出现问题。

接下来通过画图来解释这个现象:

  1. 这是开始的slice:

    slice := []int{1, 2, 2, 2, 2, 4, 5}
    fmt.Printf("切片长度:%d,容量:%d \n", len(slice), cap(slice))
    

    在这里插入图片描述

  2. 进入循环删除元素:

    for index, value := range slice {if value == 2 {slice = append(slice[:index], slice[index+1:]...)}fmt.Println(index, value)
    }
    

    在这里插入图片描述
    当index = 1时,删除第一次2后:

    在这里插入图片描述
    当index = 2时,删除第二次2后:

    在这里插入图片描述

在 Go 的 for index, val := range slice 循环中,indexval 在每次循环迭代中都会被重新赋值,以便遍历切片中的每个元素。这意味着在每次循环迭代中,indexval 都会随着切片中的元素不断变化。

例如,考虑以下代码片段:

slice := []int{1, 2, 3, 4, 5}
for index, val := range slice {fmt.Printf("Index: %d, Value: %d\n", index, val)
}

在这个循环中,index 会取遍历到的元素的索引值,val 会取遍历到的元素的值。每次循环迭代,indexval 都会随着切片中的元素变化,从 0 到切片长度减 1。

虽然 indexval 会在循环中变化,但在循环内部对它们的重新赋值不会影响切片本身。即使在循环内部修改了 indexval 的值,也不会影响切片中的元素。这是因为 indexval 是在每次迭代中以新的值被复制,不会直接影响原切片中的数据。

用文字描述就是:

// index = 0,val = 1 不删除 slice = [1,2,2,2,2,4,5],打印(index,val)=(0,1)
// index = 1,val = 2 删除   slice = [1,2(1),2(2),2,4,5],打印(index,val)=(1,2)
// index = 2,val = 2 删除   slice = [1,2(1),2,4,5],打印(index,val)=(2,2)
// index = 3,val = 4 不删除 
// index = 4,val = 5 不删除
// index = 5,val = 5 不删除
// index = 6,val = 5 不删除

index和val在循环开始时就已经确定了,所以打印时不受影响;但由于slice变化了,所以下一次循环开始时,index和val顺次增加从内存中取出的值却不是以前的值了,所以打印受到了影响。

正确的做法是,可以首先记录需要删除的元素的索引,然后再循环外面执行删除操作,避免在循环中修改切片。例如:

package mainimport "fmt"func main() {slice := []int{1, 2, 3, 4, 5}indexesToDelete := []int{}for index, value := range slice {if value == 3 {indexesToDelete = append(indexesToDelete, index)}}// 从后往前删除前面的不会受到影响for i := len(indexesToDelete) - 1; i >= 0; i-- {index := indexesToDelete[i]slice = append(slice[:index], slice[index+1:]...)}fmt.Println(slice)
}

在这个示例中,我们首先记录了需要删除的元素的索引,然后在第二个循环中进行了删除操作。这样可以避免在循环中修改切片,从而避免了索引越界和其他问题。

其他循环中删除slice元素的方法

a := []int{1, 2, 3, 4, 5},slice 删除大于 3 的数字

方法1

package mainimport "fmt"func main() {a := []int{1, 2, 3, 4, 5}for i := 0; i < len(a); i++ {if a[i] > 3 {// 当前元素被删除后,整体元素前移1位// 如果此时index++,相当于指针向后移动了两位,会导致跳过1位数组的读取// 因此,把i的自增行为抵消掉,指针不动,数组前移,i指向的地方自动会有下一个值填充进来a = append(a[:i], a[i+1:]...)i--}}fmt.Println(a)
}

方法2(推荐)

package mainimport "fmt"func main() {a := []int{1, 2, 3, 4, 5}j := 0for _, v := range a {if v <= 3 {a[j] = v// 符合条件的顺次赋值给前面的数组j++}}// 通过一次切片操作,将len置为j// 相当于只有len<=j的数组才可以看到a = a[:j]fmt.Println(a)
}

方法3

package mainimport "fmt"func main() {a := []int{1, 2, 3, 4, 5}j := 0// 相当于将a拷贝到qq := make([]int, len(a))for _, v := range a {if v <= 3 {q[j] = vj++}}q = q[:j] // q is copy with numbers >= 0fmt.Println(q)
}

官方提供的方法

go1.21版本后提供了slice库,封装了常用的slice方法:

func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {// Don't start copying elements until we find one to delete.for i, v := range s {if del(v) {j := ifor i++; i < len(s); i++ {v = s[i]if !del(v) {s[j] = vj++}}return s[:j]}}return s
}

del(v)改为v <= 3

func DeleteFunc[S ~[]int](s S) S {// Don't start copying elements until we find one to delete.for i, v := range s {if v <= 3 {j := ifor i++; i < len(s); i++ {v = s[i]if !(v <= 3) {s[j] = vj++}}return s[:j]}}return s
}

官方的操作和方法2非常相似,

func main() {a := []int{1, 2, 3, 4, 5}a = DeleteFunc(a)fmt.Println(a)a = a[:cap(a)]fmt.Println(a)
}

在这里插入图片描述

由于切片的扩缩容机制,基本上必须要把切片返回,防止切片底层指向的地址变动导致外部感受不到。

结论

  1. 当使用 for range 循环(for range) 遍历切片时,key 返回的是切片的索引,value 返回的是索引对应的值的拷贝。
  2. 在 Go 语言中,使用 for 循环删除切片元素可能会引发意外的结果,因为切片的长度在循环过程中可能会发生变化,导致索引越界或不正确的元素被删除。这是因为在删除切片元素时,删除操作会影响切片的长度和索引,从而影响后续的迭代。

切片 for 循环删除map元素

前提知识:map为什么会有这种无序性呢?map在某些条件下会自动扩容和重新hash所有的key以便存储更多的数据。 因为散列值映射到数组索引上本身就是随机的,在重新hash前后,key的顺序自然就会改变了。所以Go的设计者们就对map增加了一种随机性,以确保开发者在使用map时不依赖于有序的这个特性。

一句话:for循环中删除map元素是安全的。

官方go1.21 maps包中的删除方法:

// DeleteFunc deletes any key/value pairs from m for which del returns true.
func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) {for k, v := range m {if del(k, v) {delete(m, k)}}
}

奇怪的是,删除元素是安全的,新增元素却是不可预知的:

func main() {m := map[int]bool{0: true,1: false,2: true,}for k, v := range m {if v {m[10+k] = true}}fmt.Println(m)
}

在这里插入图片描述

上面这段代码的输出结果是不确定的。为什么呢?Go的官方文档中有这样的一段话:

If a map entry is created during iteration, it may be produced during the iteration or skipped. The choice may vary for each entry created and from one iteration to the next. – Go spec

大致的意思就是:

在遍历map期间,如果有一个新的key被创建,那么,在循环遍历过程中可能会被输出,也可能会被跳过。对于每一个创建的key在迭代过程中是选择输出还是跳过都是不同的。

也就是说,在迭代期间创建的key,有的可能会被输出,也的就可能会被跳过。这就是由于map中key的无序性造成的。

怎么解决上述问题,让输出结果变的是稳定的呢?最简单的方案就是使用复制:

m := map[int]bool{0: true,1: false,2: true,
}
m2 := make(map[int]bool)
for k, v := range m {m2[k] = vif v {m2[10+k] = true}
}
fmt.Println(m2)

由此可知,通过一个新的map,将读和写分离。即从m中读,在m2中更新,这样就能保持稳定的输出结果:

map[0:true 1:false 2:true 10:true 12:true]

goalng map delete操作不会释放底层内存

package mainimport ("fmt""runtime"
)//var a = make(map[int]struct{})func main() {v := struct{}{}a := make(map[int]struct{})for i := 0; i < 10000; i++ {a[i] = v}runtime.GC()printMemStats("添加1万个键值对后")fmt.Println("删除前Map长度:", len(a))for i := 0; i < 10000-1; i++ {delete(a, i)}fmt.Println("删除后Map长度:", len(a))// 再次进行手动GC回收runtime.GC()printMemStats("删除1万个键值对后")// 设置为nil进行回收a = nilruntime.GC()printMemStats("设置为nil后")
}func printMemStats(mag string) {var m runtime.MemStatsruntime.ReadMemStats(&m)fmt.Printf("%v:分配的内存 = %vKB, GC的次数 = %v\n", mag, m.Alloc/1024, m.NumGC)
}

可以看到,新版本的 Golang 难道真的会回收 map 的多余空间,难道哈希表会随着 map 里面的元素变少,然后缩小了?
在这里插入图片描述
将 map 放在外层:

package mainimport ("fmt""runtime"
)var a = make(map[int]struct{})func main() {v := struct{}{}//a := make(map[int]struct{})for i := 0; i < 10000; i++ {a[i] = v}runtime.GC()printMemStats("添加1万个键值对后")fmt.Println("删除前Map长度:", len(a))for i := 0; i < 10000-1; i++ {delete(a, i)}fmt.Println("删除后Map长度:", len(a))// 再次进行手动GC回收runtime.GC()printMemStats("删除1万个键值对后")// 设置为nil进行回收a = nilruntime.GC()printMemStats("设置为nil后")
}func printMemStats(mag string) {var m runtime.MemStatsruntime.ReadMemStats(&m)fmt.Printf("%v:分配的内存 = %vKB, GC的次数 = %v\n", mag, m.Alloc/1024, m.NumGC)
}

在这里插入图片描述
这时 map 好像内存没变化,直到设置为 nil。

为什么全局变量就会不变呢?

将局部变量添加一万个数,然后再删除9999个数,再添加9999个,看其变化:

package mainimport ("fmt""runtime"
)//var a = make(map[int]struct{})func main() {v := struct{}{}a := make(map[int]struct{})for i := 0; i < 10000; i++ {a[i] = v}runtime.GC()printMemStats("添加1万个键值对后")fmt.Println("删除前Map长度:", len(a))for i := 0; i < 10000-1; i++ {delete(a, i)}fmt.Println("删除后Map长度:", len(a))// 再次进行手动GC回收runtime.GC()printMemStats("删除1万个键值对后")for i := 0; i < 10000-1; i++ {a[i] = v}// 再次进行手动GC回收runtime.GC()printMemStats("再一次添加1万个键值对后")// 设置为nil进行回收a = nilruntime.GC()printMemStats("设置为nil后")
}func printMemStats(mag string) {var m runtime.MemStatsruntime.ReadMemStats(&m)fmt.Printf("%v:分配的内存 = %vKB, GC的次数 = %v\n", mag, m.Alloc/1024, m.NumGC)
}

在这里插入图片描述
这次局部变量删除后,和全局变量map一样了,内存也没变化。

但是添加10000个数后内存反而变小了。

map删除元素后map内存是不会释放的,无论是局部还是全局,但引出了上面一个奇怪的问题。

https://github.com/golang/go/issues/20135

为什么添加10000个数后内存反而变小了?因为 Golang 编译器有提前优化功能,它知道后面 map a 已经不会被使用了,所以会垃圾回收掉,a = nil 不起作用

go map原理

源码

// A header for a Go map.
type hmap struct {count     int // map元素的个数,len()的返回值flags     uint8 // 状态标识,比如正在被写、buckets和oldbuckets在被遍历、等量扩容(Map扩容相关字段)B         uint8  // B的值==log_2(buckets的长度)noverflow uint16 // 溢出桶里bmap大致的数量hash0     uint32 // hash因子buckets    unsafe.Pointer // 2^B个桶对应的指针数组的指针oldbuckets unsafe.Pointer // 旧指针,用于扩缩容nevacuate  uintptr        // 记录渐进式扩容阶段下一个要迁移的旧桶编号 extra *mapextra // 可选字段
}// bucket结构体定义type bmap struct {tophash [8]uint8 //存储哈希值的高8位keys // key数组elems // 值数组overflow *bmap   //溢出bucket的地址}type mapextra struct {overflow    *[]*bmapoldoverflow *[]*bmap// nextOverflow 持有一个指向空闲溢出桶的指针。nextOverflow *bmap
}

在这里插入图片描述

  1. tophash用来快速查找key值是否在该bucket中,而不同每次都通过真值进行比较;
  2. 根据注释(us to eliminate padding which would be needed for, e.g., map[int64]int8.),map[int64]int8,key是int64(8个字节),value是int8(一个字节),kv的长度不同,如果按照kv格式存放,则考虑内存对齐v也会占用int64,而按照后者存储时,8个v刚好占用一个int64。
    在这里插入图片描述

CRUD

将B初始化为4,则buckets为16

查询

在这里插入图片描述

  1. 计算key的hash值。

  2. 通过最后的“B”位来确定在哪号桶,此时B为4,所以取k4对应哈希值的后4位,也就是0101

  3. 根据key对应的hash值前8位快速确定是在这个桶的哪个位置

  4. 对比key完整的hash是否匹配,如果匹配则获取对应value

  5. 如果都没有找到,就去连接的下一个溢出桶中找

新增

在这里插入图片描述

  1. 通过key获取hash值
  2. hash值的低八位和bucket数组长度取余,定位到在数组中的哪个个下标
  3. hash值的高八位存储在bucket中的tophash中,用来快速判断key是否存在,key和value的具体值则通过指针运算存储,当一个bucket满时,通过overfolw指针链接到下一个bucket。

操作注意事项

map元素是无法取址的

  1. 可以得到m[key],但是无法对它的值作出任何修改,除非使用带指针的value。
  2. 因为map 会随着元素数量的增长而重新分配更大的内存空间,会导致之前的地址无效。

map是线程不安全的

某map桶数量为4,即B=2,此时 goroutine1来插入key1, goroutine2来读取 key2. 可能会发生如下过程:

  1. goroutine2 计算key2的hash值,B=2,并确定桶号为1。

  2. goroutine1添加key1,触发扩容条件。

  3. B=B+1=3, buckets数据迁移到oldbuckets。

  4. goroutine2从桶1中遍历,获取数据失败。

相关文章:

验证go循环删除slice,map的操作和map delete操作不会释放底层内存的问题

目录 切片 for 循环删除切片元素其他循环中删除slice元素的方法方法1方法2&#xff08;推荐&#xff09;方法3 官方提供的方法结论 切片 for 循环删除map元素goalng map delete操作不会释放底层内存go map原理源码CRUD查询新增 操作注意事项map元素是无法取址的map是线程不安全…...

C++二级题2

数字字符求和 #include<iostream> #include<string.h> #include<stdio.h> #include<iomanip> #include<cmath> #include<bits/stdc.h> int a[2000][2000]; int b[2000]; char c[2000]; long long n; using namespace std; int main() {ci…...

DataWhale 机器学习夏令营第三期——任务二:可视化分析

DataWhale 机器学习夏令营第三期 学习记录二 (2023.08.23)——可视化分析1.赛题理解2. 数据可视化分析2.1 用户维度特征分布分析2.2 时间特征分布分析 DataWhale 机器学习夏令营第三期 ——用户新增预测挑战赛 学习记录二 (2023.08.23)——可视化分析 2023.08.17 已跑通baseli…...

ubuntu 上安装flutter dart android studio

因为国内网站不能使用 使用一下&#xff1a; vi ~/.bashrc 最后添加 export FLUTTER_STORAGE_BASE_URLhttps://mirrors.cloud.tencent.com/flutter export PUB_HOSTED_URLhttps://mirrors.tuna.tsinghua.edu.cn/dart-pub export PATH$PATH:/usr/local/go/bin export GOPROXY…...

【Golang】go交叉编译

交叉编译是用来在一个平台上生成另一个平台的可执行程序 。Go 命令集是原生支持交叉编译的。 Mac下编译&#xff1a;Linux 或 Windows 的可执行程序 # linux 可执行程序 CGO_ENABLED0 GOOSlinux GOARCHamd64 go build main.go # Windows可执行程序 CGO_ENABLED0 GOOSwindow…...

【人工智能】—_贝叶斯网络、概率图模型、全局语义、因果链、朴素贝叶斯模型、枚举推理、变量消元

文章目录 频率学派 vs. 贝叶斯学派贝叶斯学派Probability&#xff08;概率&#xff09;:独立性/条件独立性&#xff1a;Probability Theory&#xff08;概率论&#xff09;:Graphical models &#xff08;概率图模型&#xff09;什么是图模型&#xff08;Graphical Models&…...

学习笔记:ROS使用经验( 查看rostopic的信息)

查看topic的信息 要查看ROS中的话题信息&#xff0c;你可以使用以下命令&#xff1a; 1.查看所有活动话题&#xff1a; $ rostopic list这将列出当前运行的所有活动话题。 2.查看特定话题的消息类型&#xff1a; $ rostopic info <topic_name>将<topic_name>替…...

数据库——redis内存淘汰,持久化机制

文章目录 Redis 内存淘汰机制了解么&#xff1f;⭐了解操作系统中lru并尝试用java实现lru 2.Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)快照&#xff08;snapshotting&#xff09;持久化&#xff08;RDB&#xff09;AOF&#xff08;append-only file&am…...

亚马逊云科技 云技能孵化营 我也说ai

自从chatgpt大火以后&#xff0c;我也关注了人工智能方面的东西&#xff0c;偶尔同学推荐参加了亚马逊云科技云技能孵化营活动&#xff0c;免费学习了亚马逊云科技和机器学习方面的知识&#xff0c;还获得了小礼品&#xff0c;现在将活动及心得分享给大家。 活动内容&#xff…...

『PyQt5-基础篇』| 04 Qt Designer的初步快速了解

04 Qt Designer的初步快速了解 1 Qt Designer入口2 Qt Designer-Widget Box2.1 窗口部件盒(Widget Box)2.2 Layouts布局2.3 Spacers间隔部件2.4 Button按钮2.5 Item Views(Model-Based)2.6 Item Widgets(Item-Based)2.7 Containers容器2.8 Input Widget输入部件2.9 Display W…...

SpringCloud学习笔记(十一)_Hystrix仪表盘

我们来看一下如何使用它吧 1.引入依赖 1 2 3 4 5 6 7 8 9 10 11 12 | <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </depende…...

# ruby安装设置笔记

ruby安装设置笔记 文章目录 ruby安装设置笔记1 克隆并设置环境变量2 安装ruby3 设置ruby4 设置源5 安装bundler6 检查安装后的软件版本7 ubuntu 20.04 默认ruby环境 系统自带的ruby版本低了&#xff0c;需要手动安装更高版本&#xff08;使用rbenv方式&#xff09; 环境&#x…...

关于对文件路径权限判断的记录

首先需要添加引用 using System.Security.AccessControl;以下为具体代码&#xff0c;其中fileServerPath为需要判断的文件路径 #region Authority judgmentDirectorySecurity fileAcl Directory.GetAccessControl(fileServerPath);var rules fileAcl.GetAccessRules(true, t…...

git 基础

1.下载安装Git&#xff08;略&#xff09; 2.打开git bash窗口 3.查看版本号、设置用户名和邮箱 用户名和邮箱可以随意起&#xff0c;与GitHub的账号邮箱没有关系 4.初始化git 在D盘中新建gitspace文件夹&#xff0c;并在该目录下打开git bash窗口 git init 初始化完成后会…...

C语言网络编程实现广播

1.概念 如果同时发给局域网中的所有主机&#xff0c;称为广播 我们可以使用命令查看我们Linux下当前的广播地址&#xff1a;ifconfig 2.广播地址 以192.168.1.0 (255.255.255.0) 网段为例&#xff0c;最大的主机地址192.168.1.255代表该网段的广播地址&#xff08;具体以ifcon…...

js对url进行编码解码(三种方式)

第一种&#xff1a;escape 和 unescape escape()不能直接用于URL编码&#xff0c;它的真正作用是返回一个字符的Unicode编码值 它的具体规则是&#xff0c;除了ASCII字母、数字、标点符号" * _ - . /"以外&#xff0c;对其他所有字符进行编码。在u0000到u00ff之间…...

React面向组件编程

往期回顾&#xff1a;# React基础入门之虚拟Dom【一】 面向组件编程 react是面向组件编程的一种模式&#xff0c;它包含两种组件类型&#xff1a;函数式组件及类式组件 函数式组件 注&#xff1a;react17开始&#xff0c;函数式组件成为主流 一个基本的函数组件长这个样子 …...

Linux 多线程同步机制(上)

文章目录 前言一、线程同步二、互斥量 mutex三、死锁总结 前言 一、线程同步 在多线程环境下&#xff0c;多个线程可以并发地执行&#xff0c;访问共享资源&#xff08;如内存变量、文件、网络连接 等&#xff09;。 这可能导致 数据不一致性, 死锁, 竞争条件等 问题。 为了解…...

C++学习vector

1,把list的相关函数都实现出来&#xff08;未完&#xff09; 2&#xff0c; 运行结果&#xff1a;...

17.3 【Linux】systemctl 针对 service 类型的配置文件

17.3.1 systemctl 配置文件相关目录简介 服务的管理是通过 systemd&#xff0c;而 systemd 的配置文件大部分放置于/usr/lib/systemd/system/ 目录内。但是 Red Hat 官方文件指出&#xff0c; 该目录的文件主要是原本软件所提供的设置&#xff0c;建议不要修改&#xff01;而要…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

springboot整合VUE之在线教育管理系统简介

可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生&#xff0c;小白用户&#xff0c;想学习知识的 有点基础&#xff0c;想要通过项…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

适应性Java用于现代 API:REST、GraphQL 和事件驱动

在快速发展的软件开发领域&#xff0c;REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名&#xff0c;不断适应这些现代范式的需求。随着不断发展的生态系统&#xff0c;Java 在现代 API 方…...