GO语音-切片使用的雷区与性能优化相关

news2024/10/3 10:34:57

文章目录

  • 前言
  • 一、切片是什么?
  • 二、切片使用注意项
    • 1.避免复制数组
    • 2.切片初始化
    • 3.切片GC
  • 三、切片使用注意什么
    • 1. 大家来思考一个代码示例:
    • 2. 修改切片的值
    • 3. 降低切片重复申请内存
  • 总结


前言

在 Go 语言中,切片(slice)可能是使用最为频繁的数据结构之一,切片类型为处理同类型数据序列提供一个方便而高效的方式。


一、切片是什么?

Go 的切片(slice)是在数组(array)之上的抽象数据类型,数组类型定义了长度和元素类型。例如, [3]int 类型表示由 3 个 int 整型组成的数组,数组以索引方式访问,例如表达式 s[n] 访问数组的第 n 个元素。数组的长度是固定的,长度是数组类型的一部分。长度不同的 2 个数组是不可以相互赋值的,因为这 2 个数组属于不同的类型。例如下面的代码是不合法的:

a := [3]int{1, 2, 3}
b := [4]int{2, 4, 5, 6}
a = b // cannot use b (type [4]int) as type [3]int in assignment

在 C 语言中,数组变量是指向第一个元素的指针,但是 Go 语言中并不是。Go 语言中,数组变量属于值类型(value type),因此当一个数组变量被赋值或者传递时,实际上会复制整个数组。例如,将 a 赋值给 b,修改 a 中的元素并不会改变 b 中的元素:
注释 \color{#FF0000}{注释} 注释: makemap 和 makeslice 的区别,带来一个不同点:当 map 和 slice 作为函数参数时,在函数参数内部对 map 的操作会影响 map 自身;而对 slice 却不会。

主要原因:一个是指针(*hmap),一个是结构体(slice)。Go 语言中的函数传参都是值传递,在函数内部,参数会被 copy 到本地。*hmap指针 copy 完之后,仍然指向同一个 map,因此函数内部对 map 的操作会影响实参。而 slice 被 copy 后,会成为一个新的 slice,对它进行的操作不会影响到实参。

二、切片使用注意项

1.避免复制数组

为了避免复制数组,一般会传递指向数组的指针。例如:

func square(arr *[3]int) {
	for i, num := range *arr {
		(*arr)[i] = num * num
	}
}

func TestArrayPointer(t *testing.T) {
	a := [...]int{1, 2, 3}
	square(&a)
	fmt.Println(a) // [1 4 9]
	if a[1] != 4 && a[2] != 9 {
		t.Fatal("failed")
	}
}

2.切片初始化

切片使用字面量初始化时和数组很像,但是不需要指定长度:

languages := []string{"Go", "Python", "C"}

或者使用内置函数 make 进行初始化,make 的函数定义如下:

func make([]T, len, cap) []T

第一个参数是 []T,T 即元素类型,第二个参数是长度 len,即初始化的切片拥有多少个元素,第三个参数是容量 cap,容量是可选参数,默认等于长度。使用内置函数 len 和 cap 可以得到切片的长度和容量,例如:

func printLenCap(nums []int) {
	fmt.Printf("len: %d, cap: %d %v\n", len(nums), cap(nums), nums)
}

func TestSliceLenAndCap(t *testing.T) {
	nums := []int{1}
	printLenCap(nums) // len: 1, cap: 1 [1]
	nums = append(nums, 2)
	printLenCap(nums) // len: 2, cap: 2 [1 2]
	nums = append(nums, 3)
	printLenCap(nums) // len: 3, cap: 4 [1 2 3]
	nums = append(nums, 3)
	printLenCap(nums) // len: 4, cap: 4 [1 2 3 3]
}

容量是当前切片已经预分配的内存能够容纳的元素个数,如果往切片中不断地增加新的元素。如果超过了当前切片的容量,就需要分配新的内存,并将当前切片所有的元素拷贝到新的内存块上。因此为了减少内存的拷贝次数,容量在比较小的时候,一般是以 2 的倍数扩大的,例如 2 4 8 16 …,当达到 2048 时,会采取新的策略,避免申请内存过大,导致浪费。Go 语言源代码 runtime/slice.go 中是这么实现的,不同版本可能有所差异:

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
	newcap = cap
} else {
	if old.len < 1024 {
		newcap = doublecap
	} else {
		// Check 0 < newcap to detect overflow
		// and prevent an infinite loop.
		for 0 < newcap && newcap < cap {
			newcap += newcap / 4
		}
		// Set newcap to the requested cap when
		// the newcap calculation overflowed.
		if newcap <= 0 {
			newcap = cap
		}
	}
}

3.切片GC

在这里插入图片描述
删除后,将空余的位置置空,有助于垃圾回收。

三、切片使用注意什么

1. 大家来思考一个代码示例:

// 初始化一个新的切片 seq
	seq := []string{"a", "b"}
	for k := range seq {
		if seq[k] == "a" {
			fmt.Println(k)
			seq = append(seq[:k],seq[k+1:]...)
			fmt.Println(seq)
		}
	}

请问这段代码会出现什么问题?
这段代码其实会出现代码下标越界的问题

为什么会出现这个问题呢?是因为在for循环中一遍遍历着数组,一遍去删除数组,变量seq 的len在循环开始前,仅会计算一次,如果在循环中修改切片的长度不会改变本次循环的次数。

2. 修改切片的值

请看示例:

func test03() {
	list2 := []string{"a","b"}
	for _, test := range list2 {
		test = "c"
		fmt.Println(test)
	}
	fmt.Println(list2)
}

请问输出结果是什么?最后输出的list2是 a b 还是c c

c
c
[a b]

这是为什么呢?

这是因为当for range去遍历一个数组切片的时候,新的变量test是重新分配的一块内存地址,只是将本次遍历的值赋值给了新的地址中的test变量,当我们去对 test 进行赋值的时候,并不会影响指向 list2 的指针所对应的值
如果我们需要改变 list2 中的 a 和 b 需要对代码改为:

func test03() {
	list2 := []string{"a","b"}
	for i, test := range list2 {
		list2[i] = "c"
		fmt.Println(test)
	}
	fmt.Println(list2)
}

输出结果:

a
b
[c c]

3. 降低切片重复申请内存

内存复用的例子

func test02() {
	for i := 0; i < 1000; i++ {
		buf := make([]int, 0, 3)
		for j := 0; j < 3; j++ {
			buf = append(buf, i)
		}
		buf = buf[:0] // 内存复用
		//fmt.Printf("buf, len = %d, cap = %d\n", len(buf), cap(buf))
	}

}

内存复用详细讲解

举个分页查询例子

    
	for {
	//这里的3个切片尽量放在for外面,这样可以只申请一次内存
	tmpContainers := make([]*container.Container, 0, option.Opt.MaxDeleteContainerPagingLimit)
	tmpContainerIDs := make([]int, 0, option.Opt.MaxDeleteContainerPagingLimit)
	tmpContainerHashIDs := make([]string, 0, option.Opt.MaxDeleteContainerPagingLimit)
		err = engine.Paging(container.TableContainer, "id", startID, option.Opt.MaxDeleteContainerPagingLimit).
			Where("offline is not null and cluster_id = ? and remove_time is null", config.ClusterID).
			Cols("id", "hash_id").
			Find(&tmpContainers)
		if err != nil {
			return l.WrapError(err)
		}
		if len(tmpContainers) == 0 {
			l.Warn("delete containers with warnings get containers ids len is 0")
			break
		}
		for k := range tmpContainers {
			tmpContainerIDs = append(tmpContainerIDs, tmpContainers[k].ID)
			tmpContainerHashIDs = append(tmpContainerHashIDs, tmpContainers[k].HashID)
		}

		if err = DeleteContainerRelatedData(tmpContainerHashIDs); err != nil {
			return l.WrapError(err)
		}

		rows, err := engine.Paging(container.TableContainer, "id", startID, option.Opt.MaxDeleteContainerPagingLimit).
			Unscoped().
			Where("offline is not null and cluster_id = ? and remove_time is null", config.ClusterID).
			In("id", tmpContainerIDs).
			Delete(&c)
		rowSum = rowSum + rows
		if err != nil {
			return l.WrapError(err)
		}
		if len(tmpContainerIDs) < option.Opt.MaxDeleteContainerPagingLimit {
			break
		}
		startID = tmpContainerIDs[len(tmpContainerIDs)-1]
		//这里进行内存复用,减少内存的浪费,避免内存释放缓慢的情况
		tmpContainers = tmpContainers[:0]
		tmpContainerIDs = tmpContainerIDs[:0]
		tmpContainerHashIDs = tmpContainerHashIDs[:0]
	}
func (engine *Engine) Paging(tableName, col string, start, limit int) *Session {
	sql := fmt.Sprintf("%s > %d", col, start)
	return engine.Table(tableName).Asc(col).Where(sql).Limit(limit)
}


总结

切片使用有很多的雷区,尽量别让自己踩入进去,写代码得时候也要注意性能的优化,避免为后续的开发留下悔恨的种子。和大家分享这么多关于切片的知识,如果哪里有不正确的地方,欢迎大家评论讨论

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

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

相关文章

数据结构 | 线性表

&#x1f525;Go for it!&#x1f525; &#x1f4dd;个人主页&#xff1a;按键难防 &#x1f4eb; 如果文章知识点有错误的地方&#xff0c;请指正&#xff01;和大家一起学习&#xff0c;一起进步&#x1f440; &#x1f4d6;系列专栏&#xff1a;数据结构与算法 &#x1f52…

Wise-IoU 作者导读:基于动态非单调聚焦机制的边界框损失

论文地址&#xff1a;Wise-IoU: Bounding Box Regression Loss with Dynamic Focusing Mechanism GitHub&#xff1a;https://github.com/Instinct323/wiou 摘要&#xff1a;目标检测作为计算机视觉的核心问题&#xff0c;其检测性能依赖于损失函数的设计。边界框损失函数作为…

153、【动态规划】leetcode ——416. 分割等和子集:滚动数组(C++版本)

题目描述 原题链接&#xff1a;1049. 最后一块石头的重量 II 解题思路 本题要找的是最小重量&#xff0c;我们可以将石头划分成两个集合&#xff0c;当两个集合的重量越接近时&#xff0c;相减后&#xff0c;可达到的装量就会是最小&#xff0c;此时本题的思路其实就类似于 4…

【光伏功率预测】基于EMD-PCA-LSTM的光伏功率预测模型(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

MSI_MSI-X中断之体验与使用

MSI_MSI-X中断之体验与使用 文章目录MSI_MSI-X中断之体验与使用1. 怎么发出MSI/MSI-X中断1.1 在RK3399上体验1.1.1 安装工具1.1.2 查看设备MSI-X信息1.1.3 验证MSI-X信息2. 怎么使用MSI/MSI-X3. MSI/MSI-X中断源码分析3.1 IRQ Domain创建流程3.1.1 GIC3.1.2 ITS3.1.3 PCI MSI3.…

【Flutter】【Unity】使用 Flutter + Unity 构建(AR 体验工具包)

使用 Flutter Unity 构建&#xff08;AR 体验工具包&#xff09;【翻译】 原文&#xff1a;https://medium.com/potato/building-with-flutter-unity-ar-experience-toolkit-6aaf17dbb725 由于屡获殊荣的独立动画工作室 Aardman 与讲故事的风险投资公司 Fictioneers&#x…

最大公约数:常用的四大算法求解最大公约数,分解质因数法、短除法、辗转相除法、更相减损法。

常用的四大算法求解最大公约数&#xff0c;分解质因数法、短除法、辗转相除法、更相减损法。 (本文获得CSDN质量评分【91】)【学习的细节是欢悦的历程】Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#x…

网络基础-虚拟化工具-网桥

系列文章目录 本系列文章主要是回顾和学习工作中常用的网络基础命令&#xff0c;在此记录以便于回顾。 该篇文章主要是讲解虚拟化的工具网桥相关的概念和常用命令 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录系…

C++之多态【详细总结】

前言 想必大家都知道面向对象的三大特征&#xff1a;封装&#xff0c;继承&#xff0c;多态。封装的本质是&#xff1a;对外暴露必要的接口&#xff0c;但内部的具体实现细节和部分的核心接口对外是不可见的&#xff0c;仅对外开放必要功能性接口。继承的本质是为了复用&#x…

MySQL(主从、半同步、组复制、MHA高可用)

文章目录一、MySQL源码编译以及初始化二、mysql主从复制、半同步MySQL组复制MySQL读写分离MHA高可用一、MySQL源码编译以及初始化 源码编译使用cmake&#xff0c;所以要提前安装cmake&#xff0c;完成之后make install即可 这里要创建mysql用户&#xff0c;以及用普通用户方式…

电子秤专用模拟数字(AD)转换器芯片HX711介绍

HX711简介HX711是一款专为高精度电子秤而设计的24 位A/D 转换器芯片。与同类型其它芯片相比&#xff0c;该芯片集成了包括稳压电源、片内时钟振荡器等其它同类型芯片所需要的外围电路&#xff0c;具有集成度高、响应速度快、抗干扰性强等优点。降低了电子秤的整机成本&#xff…

分享112个JS菜单导航,总有一款适合您

分享112个JS菜单导航&#xff0c;总有一款适合您 112个JS菜单导航下载链接&#xff1a;https://pan.baidu.com/s/1Dm73d2snbu15hZErJjTXxg?pwdfz1c 提取码&#xff1a;fz1c Python采集代码下载链接&#xff1a;https://wwgn.lanzoul.com/iKGwb0kye3wj base_url "h…

【游戏逆向】RPG游戏背包镶嵌系统分析

镶嵌系统是很多3D游戏都有的功能&#xff0c;玩家可以向镶嵌槽内附加宝石来提升装备的属性&#xff0c;这也直接提升了物品的价值。在一些扫拍卖和摆摊的外挂中经常利用这个属性来低价购入高价值装备。以这款游戏为例&#xff0c;我们来对装备上的镶嵌槽和镶嵌宝石进行分析。 …

Nacos,一款非常优秀的注册中心(附视频)

Nacos 核心源码精讲 - IT贱男 - 掘金小册全方位源码精讲&#xff0c;深度剖析 Nacos 注册中心和配置中心的核心思想。「Nacos 核心源码精讲」由IT贱男撰写&#xff0c;375人购买https://s.juejin.cn/ds/BuC3Vs9/ 先简单说两句 你好&#xff0c;很高兴你能够点开本小册&#x…

python 的 if 语句如何使用说明

文章目录1. 一个示例2. 条件测试2.1 检查是否相等2.2 检查是否相等时不考虑大小写2.3 检查是否不相等2.4 比较数字2.5 检查多个条件2.6 布尔表达式3. if 语句4. 使用 if 语句处理列表1. 一个示例 关于 if 条件语句的使用&#xff0c;我们来写一个示例进行说明&#xff1a; #写…

6.14 Rayleigh商

定义 矩阵在某个向量处的瑞利商Rayleigh quotient是这样定义的: ρ(x):xHAxxHx\rho(x) :\frac{x^HAx}{x^Hx} ρ(x):xHxxHAx​   这个怎么理解呢?上面是埃尔米特内积的表达式&#xff0c;下面是标准埃尔米特内积。但是矩阵不一定是对称阵&#xff0c;如果不是复数的话&#x…

ChatGPT 这个风口,普通人怎么抓住:比如APP集成ChatGPT,公众号集成ChatGPT...

文章目录1. 引出问题2. 简单介绍ChatGPT2.1 ChatGPT是什么2.2 如何使用ChatGPT3. 普通人利用ChatGPT 变现方式1. 引出问题 最近几天OpenAI发布的ChatGPT聊天机器人如日中天&#xff0c;连着上了各个平台的热搜榜。 很多平台也都已集成了ChatGPT&#xff0c;比如csdn的客户端A…

json-server使用

文章目录json-server使用简介安装json-server启动json-server操作创建数据库查询数据增加数据删除数据修改数据putpatch配置静态资源静态资源首页资源json-server使用 简介 github地址 安装json-server npm install -g json-server启动json-server json-server --watch db…

Linux系统位运算函数以及相应CPU ISA实现收录

以32位数据的二进制表示为例&#xff0c;习惯的写法是LSB在左&#xff0c;MSB在右&#xff0c;注意BIT序和大小端的字节序没有关系。Linux和BIT操作有关的接口在定义在头文件bitops.h中&#xff0c;bitops.h定义有两层&#xff0c;通用层和架构层&#xff0c;对应两个bitops.h&…

【重要】2023年上半年有三AI新课程规划出炉,讲师持续招募中!

2023年正式起航&#xff0c;想必大家都已经完全投入到了工作状态中&#xff0c;有三AI平台今年将在已有内容的基础上&#xff0c;继续进行新课程开发&#xff0c;本次我们来介绍今年上半年的课程计划&#xff0c;以及新讲师招募计划。2023年新上线课程我们平台的课程当前分为两…