小编给大家分享一下在Go中使用切片容量和长度的方法,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

在Go中使用切片容量和长度的方法

来做一个快速测验-以下代码输出什么?

vals := make([]int, 5)
for i := 0; i < 5; i++ {
 vals = append(vals, i)
}
fmt.Println(vals)

Run 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

var a [10]intlen(a)[10]int

虽然在特定情况下使用具有固定大小的数组很有价值,但通常来说这并不是开发人员想要的。相反,我们希望使用与Go中的数组类似的东西,但是具有随着时间增加长度的能力。一种简单的方法是创建一个比需要的数组大得多的数组,然后将该数组的子集当作使用的数组。下面的代码显示了一个示例。

var vals [20]int
for i := 0; i < 5; i++ {
 vals[i] = i * i
}
subsetLen := 5

fmt.Println("The subset of our array has a length of:", subsetLen)

// Add a new item to our array
vals[subsetLen] = 123
subsetLen++
fmt.Println("The subset of our array has a length of:", subsetLen)

Run it on the Go Playground → https://play.golang.org/p/Np6-NEohm2

上面代码中,我们将一个数组其大小设置为20,但是由于我们仅使用一个子集,因此我们的代码可以假装数组的长度为5,然后在向数组中添加新项后为6。

lengthsubsetLen
append
sliceappend

让我们再次看一下前面的示例,但是这次我们将使用切片而不是数组。

var vals []int
for i := 0; i < 5; i++ {
 vals = append(vals, i)
 fmt.Println("The length of our slice is:", len(vals))
 fmt.Println("The capacity of our slice is:", cap(vals))
}

// Add a new item to our array
vals = append(vals, 123)
fmt.Println("The length of our slice is:", len(vals))
fmt.Println("The capacity of our slice is:", cap(vals))

// Accessing items is the same as an array
fmt.Println(vals[5])
fmt.Println(vals[2])

Run it on the Go Playground →https://play.golang.org/p/M_qaNGVbC-

appendlencap

考虑到这一点,让我们回顾一下文章开头的测验代码,看看出了什么问题。

vals := make([]int, 5)
for i := 0; i < 5; i++ {
 vals = append(vals, i)
}
fmt.Println(vals)
make
[0 ,0 ,0 ,0 ,0]append
Println()
vals := make([]int, 5)
fmt.Println("Capacity was:", cap(vals))
for i := 0; i < 5; i++ {
 vals = append(vals, i)
 fmt.Println("Capacity is now:", cap(vals))
}

fmt.Println(vals)

Run 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; i < 5; i++ {
 vals[i] = i
}
fmt.Println(vals)

Run 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

i

这导致我们进入第二种方法

使用0作为长度,并指定容量

我们更新make调用,在切片类型之后为其提供两个参数。首先,新切片的长度将设置为0,因此我们没有在切片中添加任何新元素。第二个参数是新切片的容量,将被设置为map参数的长度,因为我们知道切片最终的长度就是 map 的长度。

append
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/h6hVAHmqJm

使用 append 能自动扩容,为什么还要关心切片的容量

你可能要问的下一件事是:“如果append函数可以为我增加切片的容量,我们为什么还要告诉程序一个容量?”

var vals []intappend

请在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

slicekeys()

不要过度优化

通常不鼓励任何人担心像这样的次要优化,但是在确实很明显最终大小应该是多少的情况下,强烈建议为切片设置适当的容量或长度。

它不仅有助于提高应用程序的性能,而且还可以通过明确说明输入大小和输出大小之间的关系来帮助理清代码。

以上是“在Go中使用切片容量和长度的方法”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注风纳云行业资讯频道!

另外有需要云服务器可以了解下风纳云fengnayun.com,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。