如何在Go中使用切片容量和长度
来源:脚本之家
时间:2022-12-28 10:30:40 218浏览 收藏
IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《如何在Go中使用切片容量和长度》,聊聊长度、容量、切片,我们一起来看看吧!
来做一个快速测验-以下代码输出什么?
vals := make([]int, 5) for i := 0; iRun it on the Go Playground → https://play.golang.org/p/7PgUqBdZ6Z
如果猜到了
[0 0 0 0 0 0 1 2 3 4]
,那么你是正确的。 等一下为什么不是[0 1 2 3 4]
?如果答错了,也不担心。从其他语言过渡到Go时,这是一个相当普遍的错误,在本文中,我们将介绍为什么输出不符合你的预期以及如何利用Go的细微差别来提高代码效率。
Slices vs Arrays
在Go中,既有数组又有切片。切片和数组之间有很多区别,数组的长度是其类型的一部分,所以数组不能改变大小,而切片可以具有动态大小,因为切片是数组的包装。这是什么意思?假设我们有一个数组
var a [10]int
。此数组的大小固定,无法更改。如果我们调用len(a)
,它将始终返回10,因为该大小10是该类型[10]int
的一部分。如果你在数组中需要10个以上的项,则必须创建一个类型完全不同的新对象,例如var b [11] int,然后将所有值从a复制到b。虽然在特定情况下使用具有固定大小的数组很有价值,但通常来说这并不是开发人员想要的。相反,我们希望使用与Go中的数组类似的东西,但是具有随着时间增加长度的能力。一种简单的方法是创建一个比需要的数组大得多的数组,然后将该数组的子集当作使用的数组。下面的代码显示了一个示例。
var vals [20]int for i := 0; iRun it on the Go Playground → https://play.golang.org/p/Np6-NEohm2
上面代码中,我们将一个数组其大小设置为20,但是由于我们仅使用一个子集,因此我们的代码可以假装数组的长度为5,然后在向数组中添加新项后为6。
(很粗略地说)这就是切片的工作方式。它们包装一个具有设定大小的数组,就像上一个示例中的数组具有20的设定大小一样。它们还跟踪程序可使用的数组子集-
length
属性,它类似于上一示例中的subsetLen
变量。切片还具有一个容量,类似于上一个示例中数组(20)的总长度。这很有用,因为它告诉你子集可以增长多大之后才能不再适合支撑切片的底层数组。当发生这种情况时,将会分配一个新的数组来支撑切片,但是所有这些逻辑都隐藏在
append
函数的后面。简而言之,将
slice
与append
函数结合在一起可以为我们提供一种与数组非常相似的类型,但是随着时间的增长,它可以处理更多元素。让我们再次看一下前面的示例,但是这次我们将使用切片而不是数组。
var vals []int for i := 0; iRun it on the Go Playground →https://play.golang.org/p/M_qaNGVbC-
我们仍然可以像访问数组一样访问切片中的元素,但是通过使用切片和
append
函数,我们不再需要考虑支持数组的大小。通过使用len
和cap
函数,我们仍然可以弄清楚这些事情,但是我们不必太担心它们。考虑到这一点,让我们回顾一下文章开头的测验代码,看看出了什么问题。
vals := make([]int, 5) for i := 0; i调用
make
时,我们最多可以传入3个参数。第一个是我们要分配的类型,第二个是类型的长度,第三个是类型的容量(此参数是可选的)。通过make([] int, 5),我们告诉程序要创建一个长度为5的切片,并且容量默认为提供的长度-在这里是5。虽然这看起来似乎是我们最初想要的,但这里的重要区别是我们告诉切片要将长度和容量都设置为5,make 将切片初始化为
[0 ,0 ,0 ,0 ,0]
然后继续调用append
函数,因此它将增加容量并在切片的末尾开始添加新元素。如果在代码中添加
Println()
语句,可以看到容量的变化。vals := make([]int, 5) fmt.Println("Capacity was:", cap(vals)) for i := 0; iRun it on the Go Playground →https://play.golang.org/p/d6OUulTYM7
结果,我们最终得到了输出
[0 0 0 0 0 0 0 1 2 3 4]
而不是期望的[0 1 2 3 4]
。 我们该如何解决?嗯,有几种方法可以做到这一点,我们将介绍其中两种,你可以择最适合自己情况的一种。不使用 append, 直接用索引写入
第一个解决方法是保持make调用不变,并明确声明要将每个元素设置为的索引。
vals := make([]int, 5) for i := 0; iRun it on the Go Playground → https://play.golang.org/p/d6OUulTYM7
我们设置的值恰好与我们要使用的索引相同,但是您也可以独立跟踪索引。 例如,如果您想获取map的key,则可以使用以下代码:
package main import "fmt" func main() { fmt.Println(keys(map[string]struct{}{ "dog": struct{}{}, "cat": struct{}{}, })) } func keys(m map[string]struct{}) []string { ret := make([]string, len(m)) i := 0 for key := range m { ret[i] = key i++ } return ret }Run it on the Go Playground → https://play.golang.org/p/kIKxkdX35B
这之所以行之有效,是因为我们知道返回的切片的确切长度将与map的长度相同,因此我们可以使用该长度初始化切片,然后将每个元素分配给适当的索引。这种方法的缺点是我们必须跟踪
i
,以便我们知道将每个值放入哪个索引。这导致我们进入第二种方法
使用0作为长度,并指定容量
我们更新make调用,在切片类型之后为其提供两个参数。首先,新切片的长度将设置为0,因此我们没有在切片中添加任何新元素。第二个参数是新切片的容量,将被设置为map参数的长度,因为我们知道切片最终的长度就是 map 的长度。
这仍将在幕后构造与上一个示例相同的数组,但是现在,当我们调用
append
时,它将知道将元素放置在切片的开头,因为切片的长度为0。package main import "fmt" func main() { fmt.Println(keys(map[string]struct{}{ "dog": struct{}{}, "cat": struct{}{}, })) } func keys(m map[string]struct{}) []string { ret := make([]string, 0, len(m)) for key := range m { ret = append(ret, key) } return ret }Run it on the Go Playground →https://play.golang.org/p/h5hVAHmqJm
使用 append 能自动扩容,为什么还要关心切片的容量
你可能要问的下一件事是:“如果append函数可以为我增加切片的容量,我们为什么还要告诉程序一个容量?”
事实是,在大多数情况下,无需太担心这一点。如果它使您的代码复杂得多,只需使用
var vals []int
初始化切片,然后让append
函数处理繁重的工作。但是针对知道切片最终长度的情况,我们可以在初始化切片时声明其容量,从而使程序不必执行不必要的内存分配。请在Go Playground上运行以下代码。每当容量增加时,我们的程序就需要执行另一次内存分配:
package main import "fmt" func main() { fmt.Println(keys(map[string]struct{}{ "dog": struct{}{}, "cat": struct{}{}, "mouse": struct{}{}, "wolf": struct{}{}, "alligator": struct{}{}, })) } func keys(m map[string]struct{}) []string { var ret []string fmt.Println(cap(ret)) for key := range m { ret = append(ret, key) fmt.Println(cap(ret)) } return ret }Run it on the Go Playground → https://play.golang.org/p/fDbAxtAjLF
现在将切片预设容量后将其与上面相同的代码进行比较:
package main import "fmt" func main() { fmt.Println(keys(map[string]struct{}{ "dog": struct{}{}, "cat": struct{}{}, "mouse": struct{}{}, "wolf": struct{}{}, "alligator": struct{}{}, })) } func keys(m map[string]struct{}) []string { ret := make([]string, 0, len(m)) fmt.Println(cap(ret)) for key := range m { ret = append(ret, key) fmt.Println(cap(ret)) } return ret }Run it on the Go Playground → https://play.golang.org/p/nwT8X9-7eQ
在第一个代码示例中,我们的容量从0开始,然后增加到1、2、4,最后是8,这意味着我们必须在5个不同的时间分配一个新数组,此外,最后一个数组用于支持我们
slice
的容量为8,大于我们最终需要的容量。 另一方面,我们的第二个示例以相同的容量(5)开始和结束,并且只需要在keys()
函数开始时分配一次即可。我们还避免浪费任何额外的内存。不要过度优化
通常不鼓励任何人担心像这样的次要优化,但是在确实很明显最终大小应该是多少的情况下,强烈建议为切片设置适当的容量或长度。
它不仅有助于提高应用程序的性能,而且还可以通过明确说明输入大小和输出大小之间的关系来帮助理清代码。
本文并不是要对切片或数组之间的差异进行详尽的讨论,而只是要简要介绍容量和长度如何影响切片以及它们在不同解决方案中的作用。
文中关于golang的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《如何在Go中使用切片容量和长度》文章吧,也可关注golang学习网公众号了解相关技术文章。
-
267 收藏
-
121 收藏
-
496 收藏
-
202 收藏
-
171 收藏
-
155 收藏
-
457 收藏
-
309 收藏
-
225 收藏
-
485 收藏
-
233 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 无限的宝贝
- 这篇文章太及时了,太详细了,很好,码起来,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2023-02-04 20:37:23
-
- 狂野的寒风
- 赞 👍👍,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢博主分享文章内容!
- 2023-01-10 13:03:15
-
- 无限的宝贝
- 这篇技术文章真是及时雨啊,博主加油!
- 2023-01-07 16:07:42
-
- 张大本事bb
- 太细致了,已加入收藏夹了,感谢楼主的这篇技术文章,我会继续支持!
- 2023-01-06 22:33:57
-
- 畅快的豆芽
- 这篇技术贴真及时,太全面了,真优秀,mark,关注博主了!希望博主能多写Golang相关的文章。
- 2022-12-30 01:47:23
-
- 英俊的海燕
- 写的不错,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢大佬分享文章内容!
- 2022-12-29 11:31:28