前言

我们都知道 Go 语言中的 slice 具有动态扩容的机制(不知道的同学请先补课 Go 切片)

但是其底层机制是什么呢?本着知其然,知其所以然的探索精神去研究一番。还不是为了应试 手动狗头

go version go1.15.6 windows/amd64

扩容

既然是八股文,哪就先说结论,切片的扩容分两步:预估扩容后的容量,确定内存占用后得到最终的容量

下文给出了一个例子,读者可以先猜测一下结果,带着问题寻找答案。不然上来就看源码分析,还不得晕

s := []int32{1, 2}
s = append(s, 3, 4, 5)
fmt.Printf("len=%d, cap=%d", len(s), cap(s))

预估容量

删除一些边界检查,溢出检查,基于 cap 的预估算法非常简单

// src/runtime/slice.go
/*
参数分析:
old 是老切片
cap 是新切片容量的最小值(即旧切片的容量加上新加入元素的数量),上面的例子中,cap 值为 5(2+3=5)
*/
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 { // 如果旧切片长度小于 1024,则新容量为旧切片容量的 2 倍
newcap = doublecap
} else {
for newcap < cap {
newcap += newcap / 4 // 每次增长 25%,直到大于最小值
}
}
}
}

按照这种算法,得出上个例子新切片的容量为 5(3+2 大于 2*2)

内存占用

内存占用 = 元素个数 * 元素类型大小。

不过,由于 Go 语言的内存分配是由其 runtime 来管理的,程序并不是直接和操作系统打交道。

在程序启动时,runtime 会提前向操作系统申请一批内存,按照不同的规格管理起来,如下所示(重点看 bytes/obj 这列):

// src/runtime/sizeclasses.go
// Code generated by mksizeclasses.go; DO NOT EDIT.
//go:generate go run mksizeclasses.go package runtime // class bytes/obj bytes/span objects tail waste max waste
// 1 8 8192 1024 0 87.50%
// 2 16 8192 512 0 43.75%
// 3 32 8192 256 0 46.88%
// 4 48 8192 170 32 31.52%
// 5 64 8192 128 0 23.44%
// 6 80 8192 102 32 19.07%
// 7 96 8192 85 32 15.95%
// 8 112 8192 73 16 13.56%
// 9 128 8192 64 0 11.72%
// 10 144 8192 56 128 11.82%
// 11 160 8192 51 32 9.73% // ......

当程序向 runtime 申请内存时,它会匹配足够大,且最接近的规格

上例中,int32 占用 4 byte,总内存占用为 5 * 4=20 byte,则 runtime 实际分配的内存为 32 byte,最终的容量为 32 / 4(每个 int 32 占用大小) = 8

练习

s := []int64{1, 2}
s = append(s, 3, 4, 5)
fmt.Printf("len=%d, cap=%d", len(s), cap(s))
  1. 2(老容量)+ 3(新添加的元素)= 5,超出 4 (老容量的两倍),即预估容量为 5

  2. int64 占用 8 byte,总内存 5 * 8 = 40 byte,runtime 实际分配 48 byte,48 / 8 = 6

参考

slice类型存什么?make和new?slice和数组?扩容规则?

终于理解了Slice扩容机制

Go slice 扩容机制分析的更多相关文章

  1. Java ArrayList源码分析(含扩容机制等重点问题分析)

    写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...

  2. 20220929-ArrayList扩容机制源码分析

    示例代码 public class ArrayListSource { public static void main(String[] args) { ArrayList arrayList = n ...

  3. ArrayList源码解析(二)自动扩容机制与add操作

    本篇主要分析ArrayList的自动扩容机制,add和remove的相关方法. 作为一个list,add和remove操作自然是必须的. 前面说过,ArrayList底层是使用Object数组实现的. ...

  4. HashMap底层结构、原理、扩容机制

    https://www.jianshu.com/p/c1b616ff1130 http://youzhixueyuan.com/the-underlying-structure-and-princip ...

  5. 浅谈JAVA中HashMap、ArrayList、StringBuilder等的扩容机制

    JAVA中的部分需要扩容的内容总结如下:第一部分: HashMap<String, String> hmap=new HashMap<>(); HashSet<Strin ...

  6. 跟大佬一起读源码:CurrentHashMap的扩容机制

    并发编程——ConcurrentHashMap#transfer() 扩容逐行分析 前言 ConcurrentHashMap 是并发中的重中之重,也是最常用的数据结构,之前的文章中,我们介绍了 put ...

  7. 【数组】- ArrayList自动扩容机制

    不同的JDK版本的扩容机制可能有差异 实验环境:JDK1.8 扩容机制: 当向ArrayList中添加元素的时候,ArrayList如果要满足新元素的存储超过ArrayList存储新元素前的存储能力, ...

  8. MongoDB Sharding 机制分析

    MongoDB Sharding 机制分析 MongoDB 是一种流行的非关系型数据库.作为一种文档型数据库,除了有无 schema 的灵活的数据结构,支持复杂.丰富的查询功能外,MongoDB 还自 ...

  9. Question 20171116 StringBuffer和StringBuilder的扩容机制

    StringBuffer和StringBuilder都是继承自AbstractStringBuilder,它们两个的区别在于buffer是线程安全的,builder是线程不安全的,前者安全效率低,后者 ...

随机推荐

  1. D - D ZOJ - 1151 (字符串操作)

    For each list of words, output a line with each word reversed without changing the order of the word ...

  2. @unittest.skip(reason):强制跳转。reason是跳转原因

    在执行测试用例时,有时候有些用例是不需要执行的,那我们怎么办呢?难道删除这些用例?那下次执行时如果又需要执行这些用例时,又把它补回来?这样操作就太麻烦了. unittest提供了一些跳过指定用例的方法 ...

  3. 12- APP接口测试以及接口文档的分析

    什么是接口? 为什么要做接口测试? 接口测试流程 需求评审 需求分析 接口用例设计 执行测试用例 bug的定位于追踪 接口文档分析 接口文档分析:开发 内容: 1.接口名称 2.接口地址 3.支持方式 ...

  4. php抽象类,接口,特性的比较

    php抽象类 抽象方法必须被子类继承实现,所以不能为私有,只能是受保护的或公有的; 抽象类子类的方法访问控制级别必须和抽象类相等或更宽松.例如,父类的抽象方法是受保护的,子类实现时则必须为受保护的或者 ...

  5. hdu4882 水贪心

    题意:      给你n个任务,每个任务有两个权值,t[i],b[i],前面的是完成任务所需时间,后面的那个是个参数,每个任务完成的代价是完成当前任务总时间(之前的+现在的) sumt * b[i], ...

  6. 编译Android 4.4.4 r1的源码刷Nexus 5手机详细教程

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/54562606 网上关于编译Android源码的教程已经很多了,但是讲怎么编译And ...

  7. 用最容易的方式学会单链表(Python实现)

    单链表与数组 在本博客中,我们介绍单链表这种数据结构,链表结构为基于数组的序列提供了另一种选择(例如Python列表). 基于数组的序列也会有如下缺点: 一个动态数组的长度可能超过实际存储数组元素所需 ...

  8. Python协程与JavaScript协程的对比

    前言 以前没怎么接触前端对JavaScript 的异步操作不了解,现在有了点了解一查,发现 python 和 JavaScript 的协程发展史简直就是一毛一样! 这里大致做下横向对比和总结,便于对这 ...

  9. 多种方法实现实现全排列 + sort调用标准函数库函数的简述

    全排列:所有不同顺序的元素组组成的一个集合.这里使用使用递归实现全排列. 使用递归算算法呢,首先我们先找一下结束的条件:我们要对一组元素(这里使用数字举例)实现全排列,临界条件就是递归到只有一个元素的 ...

  10. 使用FastDFS进行文件管理

    使用FastDFS进行文件管理 FastDFS简介 FastDFS: FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储.文件同步.文件访问(文件上传.文件下载)等, ...