golang中slice扩容的具体实现

2025-05-23 15:50

本文主要是介绍golang中slice扩容的具体实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《golang中slice扩容的具体实现》Go语言中的切片扩容机制是Go运行时的一个关键部分,它确保切片在动态增加元素时能够高效地管理内存,本文主要介绍了golang中slice扩容的具体实现,感兴趣...

Go 语言中的切片扩容机制是 Go 运行时的一个关键部分,它确保切片在动态增加元素时能够高效地管理内存。这个机制是在 Go 运行时内部实现的,涉及了内存分配、数据拷贝和容量调整。扩容的实现主要体现在 runtime.growslice 函数中。下面我们将深入分析 Go 切片的扩容机制,结合源码来进行详细解答。

1. 切片扩容的触发

切片是 Go 中的一种动态数组,它有 长度(len) 和 容量(cap)。当我们向切片添加元素时,切片的长度会增加。如果长度超出了当前的容量,Go 会自动扩容。切片扩容的过程是通过 append 函数来触发的。

apChina编程pend 函数的实现

append 函数是 Go 内置的函数,用于向切片追加元素。它的实现会首先检查当前切片的容量是否足够容纳新的元素,如果不够,它会调用 runtime.growslice 函数来扩容。

// append() 源码简化版
func append(slice []T, elems ...T) []T {
    // 1. 检查当前切片是否有足够的容量
    if len(slice)+len(elems) <= cap(slice) {
        // 如果容量足够,直接追加元素
        slice = slice[:len(slice)+len(elems)]
        copy(slice[len(slice)-len(elems):], elems)
        return slice
    }
    // 2. 如果容量不足,进行扩容
    return growslice(reflect.TypeOf(slice).Elem(), slice, len(slice)+len(elems))
}
China编程

2. runtime.growslice 函数

当切片容量不足时,append 会调用 growslice 函数来扩容。growslice 函数是 Go 运行时内部函数,负责实际的内存分配和切片扩容操作。

growslice 函数源码

// growslice 扩容的实现
func growslice(et *_type, old slice, cap int) slice {
    if cap < old.cap {
        panic("growslice: cap out of range")
    }

    // 如果元素大小为 0,直接返回新的切片
    if et.size == 0 {
        return slice{unsafe.Pointer(&zerobase), old.len, cap}
    }

    // 扩容策略:根据当前容量和预期的新容量来决定新容量
    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        const threshold = 256
        if old.cap < threshold {
            newcap = doublecap
        } else {
            for 0 < newcap && newcap < cap {
                newcap += (newcap + 3*threshold) / 4
            }
            if newcap <= 0 {
                newcap = cap
            }
        }
    }

    // 根据扩容后的容量计算所需的内存空间
    var lenmem, newlenmem, capmem uintptr
    switch {
    case et.size == 1:
        lenmem = uintptr(old.len)
        newlenmem = uintptr(cap)
        capmem = roundupsize(uintptr(newcap))
        newcap = int(capmem)
    pythoncase et.size == goarch.PtrSize:
        lenmem = uintptr(old.len) * goarch.PtrSize
        newlenmem = uintptr(cap) * goarch.PtrSize
        capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
        newcap = int(capmem / goarch.PtrSize)
    case isPowerOfTwo(et.size):
        var shift uintptr
        if goarch.PtrSize == 8 {
            shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
        } else {
            shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
        }
        lenmem = uintptr(old.len) << sjavascripthift
        newlenmem = uintptr(cap) << shift
        capmem = roundupsize(uintptr(newcap) << shift)
        newcap = int(capmem >> shift)
    default:
        lenmem = uintptr(old.len) * et.size
        newlenmem = uintptr(cap) * et.size
        capmem, _ = math.MulUintptr(et.size, uintptr(newcap))
        capmem = roundupsize(capmem)
        newcap = int(capmem / et.size)
    }

    // 分配新的内存,并拷贝旧的数据
    var p unsafe.Pointer
    if et.ptrdata == 0 {
        p = mallocgc(capmem, nil, false)
    } else {
        p = mallocgc(capmem, et, true)
    }

    memmove(p, old.array, lenmem)
    return slice{p, old.len, newcap}
}

3. 扩容的具体过程

  • 检查容量是否足够: growslice 首先会检查请求的扩容是否超过了当前切片的容量。如果新容量大于原容量的两倍,它会直接使用新容量作为目标容量。

  • 根据扩容策略计算新容量:

    • 如果切片的容量小于 256,它会直接翻倍容量(newcap = cap)。

    •  如果切片容量较大,扩容时会采取逐步增加的策略:按原容量的 1.25 倍+192 (也就是3*threshold / 4) 增长,直到满足目标容量。

  • 计算内存所需大小: growslice 会计算新切片所需的内存大小,并分配新的内存空间。对于不同类型的元素(如基本类型、指针类型等),内存分配的方式会有所不同。

  • 内存分配和数据拷贝:

    • 通过 mallocgc 分配新的内存区域,在分配空间的时候因根据标签的大小等级会向上取整,go在扩容的时候宁愿牺牲一部分内部碎片也要保证外部尽可能少的外部碎片,并使用 memmove 将原切片的数据拷贝到新的内存中。

    • 扩容后的切片会更新 指针(指向新的内存)、长度 和 容量,然后返回新的切片。

4. 扩容的性能考虑

切片扩容的过程需要进行 内存分配 和 数据拷贝,这会影响性能,尤其是在频繁扩容的场景中。

  • 频繁扩容的开销:每次扩容时,都会重新分配内存并将数据拷贝到新的内存区域,这会带来额外的时间和空间开销。

  • 容量预估:为了避免频繁扩容,最好在创建切片时预估切片需要的容量,尤其是在元素数量已知的情况下。可以使用 make([]T, 0, n) 来预分配一个足够的容量,以减少扩容操作。

5. 扩容策略的总结

  • 小容量时(比如容量小于 256),切片会按 2 倍扩容。

  • 大容量时,Go 会使用 javascript;1.25 倍扩容 + 192 的策略,确保不会过度浪费内存。

  • 对于 nil 或零容量切片,growslice 会初始化并分配合适的容量。

6. 示例:切片扩容过程

下面是一个示例,展示了切片扩容的过程:

package main

import "fmt"

func main() {
    var slice []int
    for i := 0; i < 10; i++ {
        slice = append(slice, i)
        fmt.Printf("Length: %d, Capacity: %d, Slice: %v\n", len(slice), cap(slice), slice)
    }
}

输出示例:

Length: 1, Capacity: 1, Slice: [0]
Length: 2, Capacity: 2, Slice: [0 1]
Length: 3, Capacity: 4, Slice: [0 1 2]
Length: 4, Capacity: 4, Slice: [0 1 2 3]
Length: 5, Capacity: 8, Slice: [0 1 2 3 4]
Length: 6, Capacity: 8, Slice: [0 1 2 3 4 5]
Length: 7, Capacity: 8, Slice: [0 1 2 3 4 5 6]
Length: 8, Capacity: 8, Slice: [0 1 2 3 4 5 6 7]
Length: 9, Capacity: 16, Slice: [0 1 2 3 4 5 6 7 8]
Length: 10, Capacity: 16, Slice: [0 1 2 3 4 5 6 7 8 9]

如上所示,切片的容量在每次添加元素时会根据扩容策略进行增长。初始容量为 1,当容量不足时,扩容后容量翻倍。最终,容量达到 16。

总结

Go 的切片扩容机制是通过 append 函数和 growslice 函数实现的。在扩容时,Go 会根据当前切片的容量和预期的容量进行计算,并分配新的内存空间,拷贝原有数据。扩容的策略通常是小容量时翻倍,大容量时逐步增加,保证了内存的高效使用。

到此这篇关于golang中slice扩容的具体实现的文章就介绍到这了,更多相关golang slice扩容内容请搜索编程China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.chinasem.cn)!

这篇关于golang中slice扩容的具体实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154754

相关文章

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

在Linux中改变echo输出颜色的实现方法

《在Linux中改变echo输出颜色的实现方法》在Linux系统的命令行环境下,为了使输出信息更加清晰、突出,便于用户快速识别和区分不同类型的信息,常常需要改变echo命令的输出颜色,所以本文给大家介... 目python录在linux中改变echo输出颜色的方法技术背景实现步骤使用ANSI转义码使用tpu

Python使用python-can实现合并BLF文件

《Python使用python-can实现合并BLF文件》python-can库是Python生态中专注于CAN总线通信与数据处理的强大工具,本文将使用python-can为BLF文件合并提供高效灵活... 目录一、python-can 库:CAN 数据处理的利器二、BLF 文件合并核心代码解析1. 基础合

Python使用OpenCV实现获取视频时长的小工具

《Python使用OpenCV实现获取视频时长的小工具》在处理视频数据时,获取视频的时长是一项常见且基础的需求,本文将详细介绍如何使用Python和OpenCV获取视频时长,并对每一行代码进行深入解析... 目录一、代码实现二、代码解析1. 导入 OpenCV 库2. 定义获取视频时长的函数3. 打开视频文

golang版本升级如何实现

《golang版本升级如何实现》:本文主要介绍golang版本升级如何实现问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录golanwww.chinasem.cng版本升级linux上golang版本升级删除golang旧版本安装golang最新版本总结gola

SpringBoot中SM2公钥加密、私钥解密的实现示例详解

《SpringBoot中SM2公钥加密、私钥解密的实现示例详解》本文介绍了如何在SpringBoot项目中实现SM2公钥加密和私钥解密的功能,通过使用Hutool库和BouncyCastle依赖,简化... 目录一、前言1、加密信息(示例)2、加密结果(示例)二、实现代码1、yml文件配置2、创建SM2工具

Mysql实现范围分区表(新增、删除、重组、查看)

《Mysql实现范围分区表(新增、删除、重组、查看)》MySQL分区表的四种类型(范围、哈希、列表、键值),主要介绍了范围分区的创建、查询、添加、删除及重组织操作,具有一定的参考价值,感兴趣的可以了解... 目录一、mysql分区表分类二、范围分区(Range Partitioning1、新建分区表:2、分