GoLang中的切片扩容机制

从Transformer到蛋白质结构预测:手把手解读ESM系列模型的核心原理与实战价值 本文深入解析了ESM系列模型如何将Transformer架构创新应用于蛋白质结构预测,揭示了其从氨基酸序列到三维结构的高效预测机制。通过掩码语言模型预训练和注意力机制,ESM模型不仅捕捉蛋白质序列的远程相互作用,还在蛋白质工程改造、药物发现等领域展现出巨大实战价值,显著提升了结构预测速度和精度。 阅读详情

切片的容量

[5]int 是数组,而 []int 是切片。二者看起来相似,实则是根本上不同的数据结构。

切片的数据结构中,包含一个指向数组的指针 array ,当前长度 len ,以及最大容量 cap 。在使用 make([]int, len) 创建切片时,实际上还有第三个可选参数 cap ,也即 make([]int, len, cap) 。在不声明 cap 的情况下,默认 cap=len 。当切片长度没有超过容量时,对切片新增数据,不会改变 array 指针的值。

当对切片进行 append 操作,导致长度超出容量时,就会创建新的数组,这会导致和原有切片的分离。在下例中

 

a := make([]int, 5)
b := a[0:4]
a = append(a, 1)
a[1] = 5
fmt.Println(b)
// [0 0 0 0]
fmt.Println(a)
// [0 5 0 0 0 1]</pre>

由于 a 的长度超出了容量,所以切片 a 指向了一个增长后的新数组,而 b 仍然指向原来的老数组。所以之后对 a 进行的操作,对 b 不会产生影响。

试比较

 

a := make([]int, 5, 6)
b := a[0:4]
a = append(a, 1)
a[1] = 5
fmt.Println(a, b)
// [0 5 0 0 0 1] [0 5 0 0]

本例中, a 的容量为6,因此在 append 后并未超出容量,所以 array 指针没有改变。因此,对 a 进行的操作,对 b 同样产生了影响。

扩容机制

下面看看用 a := []int{} 这种方式来创建切片会是什么情况。

 

a := []int{}
for i := 0; i < 16; i++ {
    a = append(a, i)
    fmt.Print(cap(a), " ")
}
// 1 2 4 4 8 8 8 8 16 16 16 16 16 16 16 16

可以看到,空切片的容量为0,但后面向切片中添加元素时,并不是每次切片的容量都发生了变化。这是因为,如果增大容量,也即需要创建新数组,这时还需要将原数组中的所有元素复制到新数组中,开销很大,所以GoLang设计了一套扩容机制,以减少需要创建新数组的次数。但这导致无法很直接地判断 append 时是否创建了新数组。

如果一次添加多个元素,容量又会怎样变化呢?试比较下面两个例子:

 

a := []int{}
for i := 0; i < 16; i++ {
    a = append(a, 1, 2, 3, 4, 5)
    fmt.Print(cap(a), " ")
}
// 6 12 24 24 48 48 48 48 48 96 96 96 96 96 96 96 </pre>

<pre class="cm-s-default" style="box-sizing: border-box; font-size: inherit; font-family: inherit; margin: 0px; overflow: visible; padding: 0px; border-radius: 0px; border-width: 0px; background: transparent; white-space: pre; overflow-wrap: normal; line-height: inherit; color: inherit; z-index: 2; position: relative; -webkit-tap-highlight-color: transparent; font-variant-ligatures: contextual;">a := []int{}
for i := 0; i < 16; i++ {
    a = append(a, 1, 2, 3, 4, 5, 6)
    fmt.Print(cap(a), " ")
}
// 6 12 24 24 48 48 48 48 96 96 96 96 96 96 96 96

那么,是不是说,当向一个空切片中插入 2n-1 个元素时,容量就会被设置为 2n 呢?我们来试试其他的数据类型。

 

// int8
a := []int8{}
for i := 0; i < 16; i++ {
    a = append(a, 1, 2, 3, 4, 5, 6)
    fmt.Print(cap(a), " ")
}
// 8 16 32 32 32 64 64 64 64 64 128 128 128 128 128 128

// int16
fmt.Println()
b := []int16{}
for i := 0; i < 16; i++ {
    b = append(b, 1, 2, 3, 4, 5)
    fmt.Print(cap(b), " ")
}
// 8 16 32 32 32 64 64 64 64 64 128 128 128 128 128 128

// bool
fmt.Println()
c := []bool{}
for i := 0; i < 16; i++ {
    c = append(c, true, false, true, false, false)
    fmt.Print(cap(c), " ")
}
// 8 16 32 32 32 64 64 64 64 64 128 128 128 128 128 128

// float32
fmt.Println()
d := []float32{}
for i := 0; i < 16; i++ {
    d = append(d, 1.1, 2.2, 3.3, 4.4, 5.5)
    fmt.Print(cap(d), " ")
}
// 8 16 16 32 32 32 64 64 64 64 64 64 128 128 128 128 

// float64
fmt.Println()
e := []float64{}
for i := 0; i < 16; i++ {
    e = append(e, 1.1, 2.2, 3.3, 4.4, 5.5)
    fmt.Print(cap(e), " ")
}
// 6 12 24 24 48 48 48 48 48 96 96 96 96 96 96 96 

// string
fmt.Println()
f := []string{}
for i := 0; i < 16; i++ {
    f = append(f, "1.1", "2.2", "3.3", "4.4", "5.5")
    fmt.Print(cap(f), " ")
}
// 5 10 20 20 40 40 40 40 80 80 80 80 80 80 80 80 

// []int
fmt.Println()
g := [][]int{}
g1 := []int{1, 2, 3, 4, 5}
for i := 0; i < 16; i++ {
    g = append(g, g1, g1, g1, g1, g1)
    fmt.Print(cap(g), " ")
}
// 5 10 20 20 42 42 42 42 85 85 85 85 85 85 85 85

可以看到,根据切片对应数据类型的不同,容量增长的方式也有很大的区别。相关的源码包括:src/runtime/msize.gosrc/runtime/mksizeclasses.go等。

我们再看看切片初始非空的情形。

 

a := []int{1, 2, 3}
fmt.Println(cap(a))
// 3
for i := 0; i < 16; i++ {
    a = append(a, 1, 2)
    fmt.Print(cap(a), " ")
}
// 6 12 12 12 24 24 24 24 24 24 48 48 48 48 48 48

可以看到,与刚刚向空切片添加5个int的情况一致,向有3个int的切片中添加2个int,容量增长为6。

需要注意的是,append 对切片扩容时,如果容量超过了一定范围,处理策略又会有所不同。可以看看下面这个例子。

 

a := []int{1, 2, 3, 4}
fmt.Println(cap(a))
// 4
for i := 0; i < 20; i++ {
    a = append(a, a...)
    fmt.Print(cap(a), " ")
}
// 8 16 32 64 128 256 512 1024 2560 5120 10240 20480 40960 80896 158720 310272 606208 1184768 2314240 4520960

具体为什么会是这样的变化过程,还需要从源码中寻找答案。下面是 src/runtime/slice.go 中的 growslice 函数中的核心部分。

 

// src/runtime/slice.go
func growslice(et *_type, old slice, cap int) slice {
// ...省略部分
    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }
// ...省略部分
}
  • 当需要的容量超过原切片容量的两倍时,会使用需要的容量作为新容量。
  • 当原切片长度小于1024时,新切片的容量会直接翻倍。而当原切片的容量大于等于1024时,会反复地增加25%,直到新容量超过所需要的容量。

结论

GoLang中的切片扩容机制,与切片的数据类型、原本切片的容量、所需要的容量都有关系,比较复杂。对于常见数据类型,在元素数量较少时,大致可以认为扩容是按照翻倍进行的。但具体情况需要具体分析。

为了避免因为切片是否发生扩容的问题导致bug,最好的处理办法还是在必要时使用 copy 来复制数据,保证得到一个新的切片,以避免后续操作带来预料之外的副作用。

参考文献

GOM引擎单机传奇架设全攻略——从DBC2000配置到登录器生成 本文详细解析了使用GOM引擎架设单机传奇的完整流程,从DBC2000的安装配置、服务端部署、引擎控制器设置,到登录器生成与客户端补丁放置。文章重点解决了单机架设中的常见问题,如数据库路径配置、IP地址设置及Key.lic文件处理,旨在帮助玩家快速搭建稳定的个人游戏环境,体验GM乐趣。 阅读详情

相关推荐

Go切片扩容机制详解

Go切片扩容机制详解

空山新雨后,天气晚来秋 2184

Golang原理分析:切片(slice)原理及扩容机制

Go切片的特性:切片的本质是引用底层数组头指针+当前切片长度+底层数组大小:即array、len和cap: 使用时通过make关键字来进行初始化切片分配一个新的底层数组,或者是基于指定的数组进行切片化。

BoringError的博客 2700

微生物α多样性分析-R[项目代码]

本文介绍了微生物α多样性分析的概念及其在群落生态学中的应用,重点阐述了α多样性的分类和常见指数。α多样性主要关注局域均匀生境下的物种数目,通过单样品的多样性分析可以反映微生物群落的丰度和多样性。文章详细解释了多样性指数(如Shannon和Simpson指数)和丰富度指数(如Sobs、Chao、ACE等)的含义及其计算方法。此外,文章还提供了使用R语言进行α多样性分析的代码示例,包括数据加载、多样性指数计算和结果可视化。通过箱线图展示了不同组别的Shannon指数分布,为微生物群落研究提供了实用的分析工具。

Golang Slice切片如何扩容

如果切片的容量小于1024个元素,那么扩容的时候slice的cap就乘以2;一旦元素个数超过1024个元素,增长因子就变成1.25,即每次增加原来容量的四分之一。

猫轻王的博客 4963

Go 语言切片如何扩容?(全面解析原理和过程)

Go 语言切片如何扩容?(全面解析原理和过程) 包括结构介绍、扩容时机和过程、扩容原理(版本对比)、内存对齐、总结。

小韩的博客 4727

Golang】Go Slice(切片)原理及扩容机制解析

slice知多少

Ccccjjjj174的博客 2435

golang 1.18后的切片扩容机制

切片扩容机制变化

weixin_43204583的博客 1967

golang学习】从源码看go 1.20的切片扩容机制

golang切片扩容机制

qq_42763782的博客 709

Go切片扩容机制

首先,我们创建一个数组 然后我们又声明了一个s1变量,把数组s分片,此时s1是一个切片切片有三个域,其中一个存的就是数组指针,它就指向了这个数组1号下标,然后长度为2,容量为5。 然后我们再声明一个s2变量,也是把数组s分片,此时就是如下图所示。 如果此时我们修改s的值,如果在s1和s2的范围中,s1和s2的值也会改变,因为s1和s2都指向了这个数组。 而此时如果我们通过s2 = append(s2, 999)来给s2追加一个值,会发生什么? 可见s2已经无法在原来的数组上追加了,此时再追加元素,就

KeepCodeing 2561

Golang 切片(深入了解切片底层扩容机制,部分源码,测试实战+核心用法)

切片又称动态数组,依托数组实现,可以方便地进行扩容和传递,实际使用时比数组更灵活,正因为灵活,实际使用时容易出错,避免出错的方法之一便是了解其使用原理。

g821228的博客 788

详解go 切片与map 扩容机制

映射(map)**是两种常用的数据结构,它们的扩容机制各有特点,分别针对动态数组和哈希表的特性进行优化。在 Go 语言中,**切片(slice)

wamp0001的博客 899

Golang: 切片扩容机制源码分析

切片在append的过程中,当len > cap时,会触发切片扩容机制扩容时会分配一个容量更大的底层数组,并把原来切片的元素复制到新底层数组中,再添加新元素进去。扩容机制分为两部分:计算新容量(确定大致容量)、内存对齐(获取真实的扩容后容量)。

weixin_44863451的博客 869

Go 语言切片扩容机制解析

Go 语言切片扩容机制解析

一起coding,一起嗨。 527

GolangGolang进阶系列教程--Go 语言切片是如何扩容的?

在 Go 语言中,有一个很常用的数据结构,那就是切片(Slice)。切片是一个拥有相同类型元素的可变长度的序列,它是基于数组类型做的一层封装。它非常灵活,支持自动扩容切片是一种引用类型,它有三个属性:指针,长度和容量。指针: 指向 slice 可以访问到的第一个元素。长度: slice 中元素个数。容量: slice 起始元素到底层数组最后一个元素间的元素个数。切片扩容通常是在进行切片的 append 操作时触发的。

DreamSun的博客 1862

go 切片(slice)原理及用法注意事项

go切片数据结构及 是否扩容在各种情况下的影响

rgc_520_zyl的博客 1217

Golang底层总结

Golang题目总结

qq_61465103的博客 4205

浅谈golang 切片,以及切片扩容

golang slice简介以及扩容

qq_46596746的博客 426

Golang语言系列-数组与切片

数组是每一门编程语言最基本的概念之一,但是在golang里,用到的更多的是切片切片其实是对数组的包装(结构体),包含指向数组元素的指针和切片的长度和容量。当长度和容量相等情况下,如果还要插入元素,就会引起切片扩容。很多计算机编程语言的动态数组都有扩容的概念,比如C++语言中的vector,Java中的ArrayList,而且扩容规则都类似,一般是小容量下,一次性扩容为原来的两倍,之后扩容为x倍(x一般在1.25到1.5之间)。并且往往这个扩容策略转换阈值和具体的扩容策略都是因不同语言不同版本而不一样的。

zhuohuashiyi 1059

Golang深入浅出之-切片(Slices)入门:创建、操作与扩容机制

切片(Slices)是Go语言中处理数组的灵活工具,提供了动态大小、灵活操作和自动扩容等功能。本文将深入浅出地介绍Go语言切片的创建、基本操作以及扩容机制,同时揭示相关常见问题与易错点,并通过代码示例进行说明。

zevjay的博客 1078

Golang基础-Slice扩容机制详解

切片容量每次扩容为原容量的 2 倍。

qq_38609643的博客 675

Go语言( 数组)

本文主要介绍Go语言中数组(array)及它的基本使用。 Array(数组) 数组是同一种数据类型元素的集合。 在Go语言中,数组从声明时就确定,使用时可以修改数组成员,但是数组大小不可变化。 基本语法: // 定义一个长度为3元素类型为int的数组a var a [3]int 数组定义: var 数组变量名 [元素数量]数据类型 比如:var a [5]i...

a86123969的博客 279
上一篇: Raft算法
下一篇: 基于物品 的协同过滤推荐算法(ItemCF)
jayzym
博客等级 码龄10年 170粉丝 15原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值