揭秘 Go 切片(Slice)的秘密

揭秘 Go 切片(Slice)的秘密

首页休闲益智标准切片更新时间:2024-05-09

当向切片添加新参数时,底层数组会发生什么变化?它会扩展以容纳更多元素吗?

在这篇文章中,我们将深入探讨切片的内部工作原理,以及如何利用这些知识来进行更好的内存管理和性能优化。

具体而言,我们将探索 Go 中切片的底层实现和内存管理机制。

让我们开始吧!

查看数组

要深入了解切片的结构,必须仔细查看其底层类型:数组。

func main() { a := [5]int{} fmt.Printf("%p, %p\n", &a, &a[0]) } // 0x14000018240, 0x14000018240

正如您可能已经了解的那样,数组中第一个元素的内存位置也是数组本身的内存位置(这意味着当您将数组传递给函数或赋值给变量时,您实际上是传递或赋值了第一个元素的内存地址)。

因此,数组的内存布局是连续的内存块,每个元素依次放置在相邻的位置上。

切片的结构

切片有三个主要组成部分:

这些关于切片结构的信息是从Go运行时库中获取的。现在,让我们更详细地了解一下。

type slice struct { array unsafe.Pointer len int cap int }

出于演示目的,这里有一个关于切片长度和容量概念的示例(如果你已经熟悉这些概念,可以忽略这个示例)。

func main() { original := []int{0, 1, 2, 3, 4} s := original[1:2] fmt.Println(len(s), cap(s)) } // 1 4

在这个示例中,我们可以看到切片s等于[]int{1},它的容量是从原始数组的索引1到索引4的部分计算得到的。

底层数组将会改变

需要注意的是,修改切片中的元素有时会影响到底层的数组,但并非总是如此,也不应该依赖这种行为。

在某些情况下,底层的数组可能会发生改变,导致切片也发生改变。然而,在编写代码时不应该依赖这种行为,因为它可能会导致意想不到的结果。

func main() { original := []int{0, 1, 2, 3, 4} s := original[:] fmt.Println("Same array:") s[0] = 100 fmt.Println(original, s) fmt.Println("Different array:") s = append(s, 5) s[0] = 200 fmt.Println(original, s) } // Same array: // [100 1 2 3 4] [100 1 2 3 4] // Different array: // [100 1 2 3 4] [200 1 2 3 4 5]

在实际情况中,append()函数不仅仅是用于添加元素。它还负责处理切片的分配和调整大小。

  1. 1. 它会检查切片是否有足够的容量来存储新的元素。
  2. 2. 如果容量不足,它会创建一个具有更大容量的新切片,复制原始切片的元素到新切片,并将新切片赋值给原始切片。
  3. 3. 然后,它将新的元素添加到切片中。

这是我用更简单的方式重写的append()函数版本,利用了泛型:

func append[T any](s []T, x ...T) []T { n := len(s) maxN := len(s) len(x) // If there is not enough capacity, create a new slice with larger capacity if n len(x) > cap(s) { newSlice := make([]T, maxN, maxN*2) copy(newSlice, s) // Copy the elements from the original slice to the new slice s = newSlice } s = s[:maxN] copy(s[n:], x) return s }预分配技术

重新调整切片大小在性能和内存方面可能非常昂贵,因为它需要分配一个新的切片并将所有元素复制过去。

这就是为什么在使用切片时,如果我们可以预测它们将保存的元素数量,通常最好进行预分配。这有助于提高性能并防止不必要的内存分配。

“是否可以同时使用“append()”和预分配? 使用索引赋值可能很麻烦”

是的,这是可能的。

你可以使用make()函数进行预分配切片,传入两个变量,一个用于长度,另一个用于容量,而不是只传入一个。这可以消除索引赋值的需要。

func main() { s := make([]int, 0, 3) s = append2(s, 1, 2, 3, 4) fmt.Println(s) }下一次冒险

如果您对保持对软件工程领域的最新动态感兴趣,请关注我。我将确保让您了解最新信息!

请记住,始终保持学习的状态,并享受其中的乐趣,愉快的编程

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved