验证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 的元素。然而,由于删除操作改变了切片的长度和索引,循环会出现问题。
接下来通过画图来解释这个现象:
-
这是开始的slice:
slice := []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(index, value) }
当index = 1时,删除第一次2后:
当index = 2时,删除第二次2后:
在 Go 的 for index, val := range slice
循环中,index
和 val
在每次循环迭代中都会被重新赋值,以便遍历切片中的每个元素。这意味着在每次循环迭代中,index
和 val
都会随着切片中的元素不断变化。
例如,考虑以下代码片段:
slice := []int{1, 2, 3, 4, 5}
for index, val := range slice {fmt.Printf("Index: %d, Value: %d\n", index, val)
}
在这个循环中,index
会取遍历到的元素的索引值,val
会取遍历到的元素的值。每次循环迭代,index
和 val
都会随着切片中的元素变化,从 0 到切片长度减 1。
虽然 index
和 val
会在循环中变化,但在循环内部对它们的重新赋值不会影响切片本身。即使在循环内部修改了 index
或 val
的值,也不会影响切片中的元素。这是因为 index
和 val
是在每次迭代中以新的值被复制,不会直接影响原切片中的数据。
用文字描述就是:
// 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)
}
由于切片的扩缩容机制,基本上必须要把切片返回,防止切片底层指向的地址变动导致外部感受不到。
结论
- 当使用 for range 循环(for range) 遍历切片时,key 返回的是切片的索引,value 返回的是索引对应的值的拷贝。
- 在 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
}
- tophash用来快速查找key值是否在该bucket中,而不同每次都通过真值进行比较;
- 根据注释(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
查询
-
计算key的hash值。
-
通过最后的“B”位来确定在哪号桶,此时B为4,所以取k4对应哈希值的后4位,也就是0101
-
根据key对应的hash值前8位快速确定是在这个桶的哪个位置
-
对比key完整的hash是否匹配,如果匹配则获取对应value
-
如果都没有找到,就去连接的下一个溢出桶中找
新增
- 通过key获取hash值
- hash值的低八位和bucket数组长度取余,定位到在数组中的哪个个下标
- hash值的高八位存储在bucket中的tophash中,用来快速判断key是否存在,key和value的具体值则通过指针运算存储,当一个bucket满时,通过overfolw指针链接到下一个bucket。
操作注意事项
map元素是无法取址的
- 可以得到m[key],但是无法对它的值作出任何修改,除非使用带指针的value。
- 因为map 会随着元素数量的增长而重新分配更大的内存空间,会导致之前的地址无效。
map是线程不安全的
某map桶数量为4,即B=2,此时 goroutine1来插入key1, goroutine2来读取 key2. 可能会发生如下过程:
-
goroutine2 计算key2的hash值,B=2,并确定桶号为1。
-
goroutine1添加key1,触发扩容条件。
-
B=B+1=3, buckets数据迁移到oldbuckets。
-
goroutine2从桶1中遍历,获取数据失败。
相关文章:

验证go循环删除slice,map的操作和map delete操作不会释放底层内存的问题
目录 切片 for 循环删除切片元素其他循环中删除slice元素的方法方法1方法2(推荐)方法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
因为国内网站不能使用 使用一下: 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下编译:Linux 或 Windows 的可执行程序 # linux 可执行程序 CGO_ENABLED0 GOOSlinux GOARCHamd64 go build main.go # Windows可执行程序 CGO_ENABLED0 GOOSwindow…...

【人工智能】—_贝叶斯网络、概率图模型、全局语义、因果链、朴素贝叶斯模型、枚举推理、变量消元
文章目录 频率学派 vs. 贝叶斯学派贝叶斯学派Probability(概率):独立性/条件独立性:Probability Theory(概率论):Graphical models (概率图模型)什么是图模型(Graphical Models&…...

学习笔记:ROS使用经验( 查看rostopic的信息)
查看topic的信息 要查看ROS中的话题信息,你可以使用以下命令: 1.查看所有活动话题: $ rostopic list这将列出当前运行的所有活动话题。 2.查看特定话题的消息类型: $ rostopic info <topic_name>将<topic_name>替…...

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

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

『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版本低了,需要手动安装更高版本(使用rbenv方式) 环境&#x…...

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

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

C语言网络编程实现广播
1.概念 如果同时发给局域网中的所有主机,称为广播 我们可以使用命令查看我们Linux下当前的广播地址:ifconfig 2.广播地址 以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址(具体以ifcon…...

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

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

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

C++学习vector
1,把list的相关函数都实现出来(未完) 2, 运行结果:...

17.3 【Linux】systemctl 针对 service 类型的配置文件
17.3.1 systemctl 配置文件相关目录简介 服务的管理是通过 systemd,而 systemd 的配置文件大部分放置于/usr/lib/systemd/system/ 目录内。但是 Red Hat 官方文件指出, 该目录的文件主要是原本软件所提供的设置,建议不要修改!而要…...

融云获评「创业邦 · 最具创新价值出海服务商」
点击报名,9 月 21 日融云直播课 8 月 22 日 - 23 日,创业邦主办的“2023 DEMO WORLD 全球开放式创新大会暨企业出海未来大会”在上海举行,会上发布了“创业邦 2023 出海企业创新价值 100 强”,融云荣登榜单,获评“最具…...

【中危】Apache XML Graphics Batik<1.17 存在SSRF漏洞 (CVE-2022-44729)
zhi.oscs1024.com 漏洞类型SSRF发现时间2023-08-23漏洞等级中危MPS编号MPS-2022-63578CVE编号CVE-2022-44729漏洞影响广度极小 漏洞危害 OSCS 描述Apache XML Graphics Batik 是一个开源的、用于处理可缩放矢量图形(SVG)格式图像的工具库。 受影响版本中࿰…...

AssemblyManager 程序集管理器
AssemblyManager 程序集管理器 程序执行中使用反射对框架的搭建有着强大的影响,如何管理程序集方便使用反射获取类型操作对象是本文章的重点 1.AssemblyInfo 对于一个程序集这里使用一个AssemblyInfo对象进行管理 Assembly :对应的程序集AssemblyTyp…...

几个nlp的小任务(生成式任务——语言模型(CLM与MLM))
@TOC 本章节需要用到的类库 微调任意Transformers模型(CLM因果语言模型、MLM遮蔽语言模型) CLM MLM 准备数据集 展示几个数据的结构...

单元测试用例mock的使用方法
单元测试用例mock的使用方法 提升代码测试覆盖率的关键策略 为什么单元测试是如此重要? 在软件开发中,单元测试是一个关键的环节,可以确保代码的质量和稳定性。而在进行单元测试时,使用mock对象可以帮助我们更好地测试代码逻辑…...

3D步进式漫游能够在哪些行业应用?
VR技术一直以来都是宣传展示领域中的热门话题,在VR全景技术的不断发展下,3D步进式漫游技术也逐渐覆盖各行各业,特别是在建筑、房产、博物馆、企业等领域应用更加广泛,用户通过这种技术能够获得更加直观、生动、详细的展示体验&…...

2023蓝帽杯初赛ctf部分题目
Web LovePHP 打开网站环境,发现显示出源码 来可以看到php版本是7.4.33 简单分析了下,主要是道反序列化的题其中发现get传入的参数里有_号是非法字符,如果直接传值传入my_secret.flag,会被php处理掉 绕过 _ 的方法 对于__可以…...

vue3+ts封装弹窗,分页封装
定义defaultDialog .vue <script lang"ts" setup> import { ref,toRefs,onUpdated } from vue import { ElMessageBox } from element-plus const props defineProps({//接收参数,父组件传的时候用:msg123的形式msg:String,show:{type:Boolean,defa…...

2023-08-30 数据库-并发控制-冲突可串行化调度-是否可串行化检测-优先图-分析
摘要: 将冲突进行可串行化调度, 是解决冲突是一个基本功能. 对于冲突是否可被串行化调度, 比较有效的就是优先图的方法. 本文对检测冲突可串行化调度的优先图做一些分析. 上下文参考: 2023-08-30 数据库-并发控制-冲突可串行化的调度-思考_财阀悟世的博客-CSDN博客 事务的基…...

人员着装识别算法 yolo
人员着装识别系统通过yolo网络模型识别算法,人员着装识别系统算法通过现场安装的摄像头识别工厂人员及工地人员是否按要求穿戴着装,实时监测人员的着装情况,并进行相关预警。目标检测架构分为两种,一种是two-stage,一种…...