[Go] Slice的底层自动扩容
发布日期:2021-05-18 08:08:09 浏览次数:25 分类:精选文章

本文共 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在高性能应用开发中脱颖而出的重要原因之一。

上一篇:[Go] GO中的字符串底层数据结构
下一篇:[PHP] class_exists类不存在时会调用__autoload函数

发表评论

最新留言

感谢大佬
[***.8.128.20]2025年04月19日 02时00分05秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

aspen串联反应怎么输入_如何进步提升串联谐振试验装置的稳定性 2023-01-24
aspose html转pdf_Java实现Word/Pdf/TXT转html 2023-01-24
a推b等价于非a或b_AB胶/蜜月胶常见问题的原因分析及解决方法 2023-01-24
bat 命令返回结果_【批处理】带你入门命令行 2023-01-24
c++ string取子串_Integer与String的设计哲学 2023-01-24
c++ 数组批量赋值_数组之间不能赋值?穿个马甲吧! 2023-01-24
cad模糊查询符号_mysql 正则模式和like模糊查询 2023-01-24
continue可以用if判断里面吗_谁能说说if()else()里的continue是干嘛的? 2023-01-24
ctrl c 和 ctrl v 不能用了_神奇操作,原来CTRL键还能这么用 2023-01-24
cytoscape安装java_Cytoscape史上最全攻略 2023-01-24
c语言程序设计年历显示,C语言程序设计报告《万年历》.doc 2023-01-24
C语言程序设计梁海英答案,1.5 习题 2023-01-24
c语言编写单片机中断,C语言AVR单片机中断程序写法 2023-01-24
#pragma region、{} 2023-01-24
ddr2的上电顺序_S5PV210 DDR2初始化 28个步骤总结 2023-01-24
deque stack java_「集合系列」- 初探 java 集合框架图 2023-01-24
easyexcel 导出 代码翻译converter_【starter推荐】简单高效Excel 导出工具 2023-01-24
echarts 如何在一条柱形显示两个数字_干货 | 如何快速制作数据地图?让你的可视化逼格再高一级!... 2023-01-24
eclipse设置utf8编码_记住没:永远不要在 MySQL 中使用 UTF8 2023-01-24
eclipse里source的快捷方法_Eclipse快捷键/快捷操作汇总 2023-01-24