GO语音-切片使用的雷区与性能优化相关
创始人
2024-05-25 10:31:22

文章目录

  • 前言
  • 一、切片是什么?
  • 二、切片使用注意项
    • 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. 大家来思考一个代码示例:

// 初始化一个新的切片 seqseq := []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 + rowsif 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)
}

总结

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

相关内容

热门资讯

苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
怎么样的桃花 桃花近看远看的样... 今年的“三八”妇女节,阳光明媚,踏青赏花正当时。遂与姐妹们共赴十里蓝山,与花海来一场春天的约会。十里...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
怎么样的桃花 桃花近看远看的样... 今年的“三八”妇女节,阳光明媚,踏青赏花正当时。遂与姐妹们共赴十里蓝山,与花海来一场春天的约会。十里...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...