博客
关于我
[Go] Slice的底层自动扩容
阅读量:668 次
发布时间:2019-03-15

本文共 1868 字,大约阅读时间需要 6 分钟。

在Golang语言圣经中,关于数组和Slice的解释有以下要点:

一个Slice实际上是一个轻量级的数据结构,它允许我们轻松访问数组中的子序列(甚至是整体)。Slice的底层实际上是引用了一个数组对象。Slice的结构由三个部分构成:指针、长度和容量。

首先,指针指向Slice实际访问的数组中的第一个元素。这一点需要注意的是,Slice的第一个元素并不一定是数组的第一个元素。这意味着Slice可以从任意位置开始访问数组的元素。

其次,长度表示Slice中当前存储的元素数量。而容量则决定了Slice可以扩展的最大程度。容量是从Slice的当前位置到底层数组末尾的总长度。Golang内置的len和cap函数可以用来获取Slice的长度和容量。

在Golang中,创建Slice有一个特殊的函数make,是make([]T, len, cap)。这个函数会创建一个匿名数组并将其返回作为Slice。虽然Slice引用底层数组的前len个元素,但它的容量却包含整个数组的长度,并保留了额外空间供后续扩展使用。

Slice在扩展元素时,特别是在append操作中,会优化容量管理。为了提升性能,Golang使用了一种叫做_DOUBLE_O Automatically Allocated doubling策略。具体来说,当需要扩展时,Slice会为新分配的空间设置原来的两倍容量,这样在未来添加更多元素时,可以减少内存分配和复制操作的次数,从而保证平均的O(1)时间复杂度。

以下是一个伪代码示例展现Slice扩容的逻辑:

func appendInt(x []int, y int) []int {    zlen := len(x) + 1    if zlen <= cap(x) {        z = x[:zlen]    } else {        zcap := zlen        if zcap < 2 * len(x) {            zcap = 2 * len(x)        }        z = make([]int, zlen, zcap)        copy(z, x)    }    z[len(x)] = y    return z}

这个函数响应了每次在Slice尾部添加一个新元素的需求。当当前元素的长度加上新元素不会超过原有容量时,直接复制扩展即可。然而,当容量不足时,会为新Slice分配当前长度的两倍容量,以确保未来的增长空间。这既能避免频繁的内存分配,也能保证appending单个元素的操作平均时间保持在O(1)。

基于以上机制,我们可以理解为什么Golang的Slice在内存管理上非常高效。通过预先分配足够的容量,并在需要时动态扩展,最终确保了Slice在大多数情况下的性能表现。

接下来,我们来看看一个实际应用示例:

func main() {    var x, y []int    for i := 0; i < 10; i++ {        y = appendInt(x, i)        fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y)        x = y    }}

每次循环中,y被赋值为x的新扩展版本,并打印在该时刻的容量和内容。在这个过程中,我们可以看到y的容量如何随着元素添加而逐步扩大:

  • cap=1 -> [0]
  • cap=2 -> [0, 1]
  • cap=2 -> [0, 1](容量保持不变)
  • cap=4 -> [0, 1, 2]
  • cap=4 -> [0,1,2,3]
  • cap=8 -> [0,1,2,3,4]
  • cap=8 -> [0,1,2,3,4,5]
  • cap=8 -> [0,1,2,3,4,5,6]
  • cap=16 -> [0,1,2,3,4,5,6,7]
  • cap=16 -> [0,1,2,3,4,5,6,7,8]

通过这个过程可以看出,每当容量不够时,会进行一次内存重新分配,容量被设置为当前长度的两倍。这种预防性分配策略,使得内存分配的开销被分摊到多次扩展之前,从而平衡了内存分配的平均成本。

总的来说,Golang中的_slice_机制不是一个简单的数组切片,而是一个经过深思熟虑的数据结构。它不仅保证了高效的性能,还为Go语言的内存管理和.zero-initialized策略提供了有力支撑。这也正是Golang在高性能应用开发中脱颖而出的重要原因之一。

转载地址:http://kvimz.baihongyu.com/

你可能感兴趣的文章
Node-RED中连接Mysql数据库并实现增删改查的操作
查看>>
Node-RED中通过node-red-ui-webcam节点实现访问摄像头并截取照片预览
查看>>
Node-RED中配置周期性执行、指定时间阶段执行、指定时间执行事件
查看>>
Node-RED安装图形化节点dashboard实现订阅mqtt主题并在仪表盘中显示温度
查看>>
Node-RED怎样导出导入流程为json文件
查看>>
Node-RED简介与Windows上安装、启动和运行示例
查看>>
Node-RED订阅MQTT主题并调试数据
查看>>
Node-RED通过npm安装的方式对应卸载
查看>>
node-request模块
查看>>
node-static 任意文件读取漏洞复现(CVE-2023-26111)
查看>>
Node.js 8 中的 util.promisify的详解
查看>>
node.js debug在webstrom工具
查看>>
Node.js Event emitter 详解( 示例代码 )
查看>>
Node.js GET、POST 请求是怎样的?
查看>>
Node.js HTTP模块详解:创建服务器、响应请求与客户端请求
查看>>
Node.js RESTful API如何使用?
查看>>
node.js url模块
查看>>
Node.js Web 模块的各种用法和常见场景
查看>>
Node.js 之 log4js 完全讲解
查看>>
Node.js 函数是什么样的?
查看>>