Go切片与技巧(附图解)
Posted 太白技术
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go切片与技巧(附图解)相关的知识,希望对你有一定的参考价值。
你好,我是太白,今天和你聊聊Go语言的切片(Slice)以及它的一些技巧。
如果觉得文章对你有帮助,记得点赞+收藏+关注+转发。谢谢!
1 前言
2 数组
3 切片的创建
4 切片操作
5 切片表达式
1、简单的表达式
2、扩展表达式
6 切片技巧
Cut
Cut(GC)
Delete
Delete(GC)
Delete without preserving order
Delete without preserving order(GC)
Expand
Extend
Filter (in place)
Insert
InsertVector
Push
Pop
Push Front/Unshift
Pop Front/Shift
7 切片额外技巧
Filtering without allocating
Reversing
Shuffling
Batching with minimal allocation
In-place deduplicate (comparable)
Move to front, or prepend if not present, in place if possible.
Sliding Window
总结
切片是Go语言中最多被使用的数据结构之一。本文将介绍切片的创建、操作、表达式以及使用的技巧。
slice类型是建立在 Go 的数组类型之上的抽象,因此要理解 slice,我们必须首先理解数组。
先看一段代码:
完成了数组的初始化,表示一个由四个整数组成的数组。
a[1]
的时候输出0。a[0] = 1
。a[4]
会报数组越界的错误。Go的数组是值类型
。不像C语言数组变量是指向第一个元素的指针,所以当我们把数组变量传递或者赋值的时候,其实是做copy的操作。比如下面的例子a赋值给b,修改a中的元素,并没有影响b中的元素:
由于数组需要固定长度,很多时候不是很灵活,而切片更灵活、更强大。
切片包含对底层数组的引用
,如果将一个切片分配给另一个切片,则两者都引用同一个数组。切片底层的数据结构(src/runtime/slice.go):
内置函数
append()
用于向切片中追加元素。Go语言提供了两种切片的表达式:
Go在Github的官方Wiki上介绍了切片的技巧
变量声明
、切片字面量
、make创建
、new创建
、从切片/数组截取
。len()
计算长度、cap()
计算容量、append()
来添加元素。Go Slices: usage and internals: https://go.dev/blog/slices-intro
[2]Effective Go: https://go.dev/doc/effective_go#arrays
[3]go1.2#three_index: https://tip.golang.org/doc/go1.2#three_index
[4]SliceTricks: https://github.com/golang/go/wiki/SliceTricks
[5]Go Slice Tricks Cheat Sheet: https://ueokande.github.io/go-slice-tricks/
[6]Shuffle: https://pkg.go.dev/math/rand#Shuffle
你知道的Go切片扩容机制可能是错的
你好,我是太白,今天和你探索下Go语言的切片扩容机制。
如果觉得文章对你有帮助,记得点赞+关注。目前本公众号没有评论功能,有问题请联系我。
1 前言
2 代码片段1
3 代码片段2
4 growslice源代码
5 代码片段3
6 go1.18beta1的变化
7 总结
上一篇《Go切片与技巧(附图解)》,我们讲到了Go内置函数append
操作,当append操作的时候,切片容量如果不够,会触发扩容。
关于Go切片的扩容机制,网上文章很多,很多结论是这样的:
结论1:
结论2:
内存对齐
,容量计算完了后还要考虑到内存的高效利用,进行内存对齐。其中结论1,可能最让人熟知。
本文将和你一起来探索下这个切片的扩容机制,看看到底是不是这样的。
我们来看下下面的这端代码,执行结果是6,为什么不是8呢?这个和前言提到的结论1貌似有冲突。
为了解释上面的代码片段2,我们开看下go的runtime帮我们干了什么。
我们可以通过delve来调式我们的Go代码。这边演示的Go版本:
1.16.5
。通过打断点
b main.main
、然后用si
指令定位到以下代码,runtime.growslice()
正式Go扩容的源代码。带入变量变成
capmem = roundupsize(5 * 8)
,所以主要是roundupsize
影响了最终的结果。通过debug,我们发现执行到了
return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]])
特别提下,下面两端切片类型不同,扩容的结果也不同,大家可以自行debug下,所以前言提到的结论1中翻倍的结论也是有问题的。
上面分析是基于go1.16.5的,太白注意到go1.18之后,
growslice
改了
1024
变成了256
,公式也改了,newcap += newcap / 4
变成了newcap += (newcap + 3*threshold) / 4
,这边我就不展开了。给大家贴下代码:1、前言中的结论1讲法不严谨,讲对了一部分(代码片段1可知), 不同的切片类型,扩容值可能是不同的
(代码片段3可知),Go的runtime分配内存的时候,会调用roundupsize
,取整内存值(代码片段2可知)。2、前言中的结论2比结论1好一点,但它是基于结论1的,随着Go版本的迭代,这个结论也会过时。 3、Go切片的扩容机制还是比较复杂的,受到Go版本、操作系统、数据类型等因数,其机制都会有不同,要具体情况具体分析,不要单一的记下一个结论就觉得ok。 4、网上的文章参差不齐,大家在搜资料的时候,需要我们去甄别,最好自己去验证下。
读完文章如果对你有帮助,记得关注下方
以上是关于Go切片与技巧(附图解)的主要内容,如果未能解决你的问题,请参考以下文章
《Go语言精进之路》读书笔记 | 了解切片实现原理并高效使用