go切片不是动态数组

go切片不是动态数组

go 的切片设计的非常巧妙。它不仅看起来包括使用都非常像动态数组,并且对它进行了性能优化。然而,不了解其中原理可能会给你带来麻烦。

go切片

go中切片的概念是非常巧妙地。切片表示灵活长度的类似数组的数据类型,同时也提供了对内存的完全控制。

这个概念在别的语言中没有见过,所以刚刚接触go的人对切片的用法感到非常的困惑。了解切片内部原理可以消除你的困惑。所以,什么是切片?它又是如何工作的?

切片只是数组的一个小窗口

在go中,数组有一个固定的大小。在定义数组的时候,就需要设置好大小,所以这两个数组 [10]int 和 [20]int 不仅是两个大小不同的 int 数组,而且实际上是不同的数据类型。

切片在数组上面添加了一层动态层。从数组创建切片既不分配新内存也不复制任何内容。切片什么都没有,它只是数组上的一个小窗口。技术上来说,切片可以看作是一个结构体,它有一个指针指向切片开始的数组元素,已经两个描述长度和容量的整数。

这意味着切片操作的代价非常便宜。创建切片,扩展切片,在底层数组上来回移动它,所有的这些操作,只需要更改指针或两个整数类型的值。数据位置不变。

在这里插入图片描述

这也意味着从同一个数组创建的两个切片是可以重叠的,将切片分配给新切片变量后,两个变量现在共享相同的内存单元。改变其中一个切片元素的值,另一个切片也会收到影响。如果你想要真正的拷贝一个切片,先创建一个新的空切片,然后使用内置函数 copy()

所有这些都基于简单而一致的机制。当不了解这些机制时,就会出现问题。

切片是就地操作

由于切片只是静态数组上的高效“动态窗口”,大多数切片操作也发生在适当的位置确实是有道理的。

例如,bytes.Splite() 需要一个切片和分隔符,通过分隔符分割切片,并返回一个字节切片的切片。

但是:Split() 返回的所有字节切片仍然指向与原始切片相同的底层数组。对于那些从其他依赖于分配和复制语义的语言,了解类似拆分函数的人来说,这可能是出乎意料的。

忽略 Split() 的结果仍然指向原始数据这一事实的代码可能会导致数据损坏,编译器和运行时都无法检测到错误。

组合使用 bytes.Split() 和 append() 时可能会发生另一个意外行为,让我们单独看一下 append()。

append() 增加了一些便利性还有一些“魔法”

append() 在切片的末尾增加了新的元素,从而扩展了切片。

append() 拥有两个便利的功能:

  • 首先,它可以追加到一个nil的切片上,追加的那一刻就切片存在了。
  • 第二,如果剩余的容量不足以追加新值,append() 会自动分配一个新的数组并将内容复制。

特别是第二个会引发混乱,因为 append() 之后,有时原始数组会被改变。并且有时一个新的数组将被创建,并且原来的保持不变。如果原始数组被代码的不同部分引用,那么引用可能指向旧的数据。

在这里插入图片描述

这种行为很容易被描述为“随机的”,尽管结果是非常明确的。一个总是知道切片长度,容量和要追加元素的个数的观察者,它可以轻松确定追加元素是否需要分配一个新的数组。

在GO1.10之前,当 append() 到返回的第一个切片时,切片在同一个底层数组中增长,覆盖后续切片。返回的多个值共用一个底层数组。

在这里插入图片描述

在GO1.10之后,bytes.Split() 返回所有切片,其容量设置为自己的长度,而不是底层数组的长度。 append() 因此无法再覆盖后续切片。

) 返回所有切片,其容量设置为自己的长度,而不是底层数组的长度。 append() 因此无法再覆盖后续切片。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值