go 语言中的动态数组(slice),是基于数组实现的,可以相比数组而言更加的灵活。其他语言的 slice 通常仅是一个 API, 但是 go 语言的 slice 不仅仅是一种操作, 也是一种数据结构。

我们先看一下 slice 的数据结构:

type slice struct {
array unsafe.Pointer // 数组指针
len int // 切片长度
cap int // 数组容量
}

源码链接:

https://github.com/golang/go/blob/c379c3d58d5482f4c8fe97466a99ce70e630ad44/src/runtime/slice.go#L15

非常简单的结构组成: 数组指针, 长度, 容量。

slice 的操作:

初始化有 4 种方式:

// 1. 变量声明
var slice []int // 2. 字面量
slice := []int{} // 空的切片
slice2 := []int{1, 2} // 长度为 2 的切片 // 3. 通过 make 创建 slice // 创建一个存储 int 类型的
// len = 10, cap = 20
slice := make([]int, 10, 20) // 4. 通过数组切片, 此时 slice 会与数组共用底层内存 // 获取 array[3][4] 的数据, 且共用 array 后续的存储空间.
// 所以 slice 的 len = 2, cap = 10 - 3 = 7
array := [10]int
slice := array[3:5]

扩容:

当 len > cap 时, slice 会触发扩容, 提高 cap,也就是实际数组容量。

我们来看一下 slice 扩容操作:

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
const threshold = 256
if old.cap < threshold {
// 小的 slice 扩容时 cap 直接翻倍
newcap = doublecap
} else {
// 检测溢出和无限循环
for 0 < newcap && newcap < cap {
// 大的 slice 扩容时扩充 1.25 倍
newcap += (newcap + 3*threshold) / 4
}
if newcap <= 0 {
newcap = cap
}
}
}

源码地址:

https://github.com/golang/go/blob/c379c3d58d5482f4c8fe97466a99ce70e630ad44/src/runtime/slice.go#L188

针对 cap < 256 的 slice,扩容时 newcap = cap * 2

针对 cap >= 256 的 slice, 扩容时 newcap = (cap + 3 * 256) / 4

注意:网上很多文章会写阈值为 1024, 且针对较大的 slice, 采取 1.25 倍扩容策略, 但这种算法是低版本 go 中的计算方式。

那么我们来思考一下, 为什么要这么实现呢?

因为针对较小的 slice,每次扩容增加较充足的容量可以减少内存重分配的次数以及数据迁移的成本。

针对较大的 slice, 每次扩容增加相对较少的容量可以避免内存资源浪费。

添加元素

slice := make([]int, 0)
slice = append(slice, 1) // 添加 1 个元素
slice = append(slice, 2, 3, 4) // 添加多个元素
slice = append(slice, []int{5, 6}...) // 添加一个切片

如果 cap >= len + 1,则直接追加元素到 slice 中, len++,返回 slice。

如果 cap < len + 1, 则扩容 slice 得到新的 slice, 然后追加元素到新的 slice, 新的 slice.len++, 返回新的 slice。

我们可以发现 slice 的操作相比其他数据结构要更加容易理解, 但在使用的时候一定要注意由于与底层数组是通过指针引用导致的共享内存问题。

关于 go 语言中 slice 相关的基础知识就介绍这么多了~

如果您在实际开发过程中遇到过 Python、Golang、数据库、爬虫、分布式、消息队列等方面的难题, 也欢迎在公众号或评论区留言, 我们一起探讨解决方案

如果本篇内容能够帮助到您, 希望您关注我的公众号: 「python 学习爱好者」, 希望与您一起共同成长~

Go 语言入门 3-动态数组(slice)的特性及实现原理的更多相关文章

  1. (待续)C#语言中的动态数组(ArrayList)模拟常用页面置换算法(FIFO、LRU、Optimal)

    目录 00 简介 01 算法概述 02 公用方法与变量解释 03 先进先出置换算法(FIFO) 04 最近最久未使用(LRU)算法 05 最佳置换算法(OPT) 00 简介 页面置换算法主要是记录内存 ...

  2. C语言基础 - 实现动态数组并增加内存管理

    用C语言实现一个动态数组,并对外暴露出对数组的增.删.改.查函数 (可以存储任意类型的元素并实现内存管理) 这里我的编译器就是xcode 分析: 模拟存放 一个 People类 有2个属性 字符串类型 ...

  3. C语言实现使用动态数组实现循环队列

    我在上一篇博客<C语言实现使用静态数组实现循环队列>中实现了使用静态数组来模拟队列的操作. 因为数组的大小已经被指定.无法动态的扩展. 所以在这篇博客中,我换成动态数组来实现. 动态数组能 ...

  4. C语言实现使用动态数组来构造栈结构

    我在面前一篇博客<C语言实现使用静态数组来构造栈结构>中使用了静态数组来模拟栈的操作.静态数组的大小是在代码中写死的.是存储在用户栈上面的,使用起来不灵活.在这篇博客中我会使用动态数组来构 ...

  5. 纯C语言(C89)实现动态数组

    起因 工作很少接触纯C项目,业余写着玩玩,不断雕琢 目标 纯C实现动态数组,提供方便易用泛型接口,避免依赖 实现 完全封装,隐藏结构体细节,不支持栈创建 拷贝存储,轻微性能代价换来易用性 vector ...

  6. 算法入门 - 基于动态数组的栈和队列(Java版本)

    之前我们学习了动态数组的实现,接下来我们用它来实现两种数据结构--栈和队列.首先,我们先来看一下栈. 什么是栈? 栈是计算机的一种数据结构,它可以临时存储数据.那么它跟数组有何区别呢? 我们知道,在数 ...

  7. 使用java语言实现一个动态数组(详解)(数据结构)

    废话不多说,上代码 1.从类名开始(我真是太贴心了) public class Array<E> 首先数组类需要带有泛型,这个不多说.需要注意的是在java中,数组只能存放同一个类型的. ...

  8. C语言入门:一维数组的概要

    数组的概念: 具有相同数据的有序集合 一维数组的定义格式: int a[5]; 类型说明符  数组名(标识符)[常量表达式(长度)]; 一维数组下标 : 数组的下标 从0开始  最大下标值 为 数组的 ...

  9. C++ Primer : 第十二章 : 动态内存之动态数组

    动态数组的分配和释放 new和数组 C++语言和标准库提供了一次分配一个对象数组的方法,定义了另一种new表达式语法.我们需要在类型名后跟一对方括号,在其中指明要分配的对象的数目. int* arr ...

随机推荐

  1. C++ 之多态总结

    前言 最近为了完成数据库系统的实验,又复习起了<C++ Primer>,上一次看这本巨著也是大二下的六月份,那时看面向对象程序编程这一章还云里雾里的,没有领会多态的奥妙,学完 Java 之 ...

  2. alertmanager集群莫名发送resolve消息的问题探究

    alertmanager集群莫名发送resolve消息的问题探究 术语 告警消息:指一条告警 告警恢复消息:指一条告警恢复 告警信息:指告警相关的内容,包括告警消息和告警恢复消息 问题描述 最近遇到了 ...

  3. 【Java面试】@Resource 和 @Autowired 的区别

    Hi,大家好,我是Mic. 一个工作2年的粉丝,问我一个Spring里面的问题. 希望我能从不同的视角去分析,然后碾压面试官. 这个问题是: "@Resource和@Autowired&qu ...

  4. C# · 委托语句简化演变

    1.委托基础语句形式 namespace QLVision { delegate void dHelp();//定义委托 static class Program { /// <summary& ...

  5. python小题目练习(七)

    题目:实现如下图所示结果 代码实现: """Author:mllContent:模拟火车订票系统Date:2020-11-16"""# 定义 ...

  6. 初始化二维列表时使用[ [0]* N ] * K会出现的问题

    声明二维列表使用[ [0]* N ] * K会出现的问题 初始化二维列表时使用[ [0]* N ] * K创建,外层列表的每一个元素地址相同: 创造了一个二维列表: 修改其中的一个元素a[1][1], ...

  7. 再见Docker!Containerd安装与使用

    Containerd 的技术方向和目标 简洁的基于 gRPC 的 API 和 client library 完整的 OCI 支持(runtime 和 image spec) 同时具备稳定性和高性能的定 ...

  8. NOI / 2.1基本算法之枚举-8759:火车上的人数

    8759:火车上的人数​​​​​​ 总时间限制: 1000ms 内存限制: 65536kB 描述 火车从始发站(称为第1站)开出,在始发站上车的人数为a,然后到达第2站,在第2站有人上.下车,但上.下 ...

  9. 温控器/胎压检测/电表/热泵显示控制器等,低功耗高抗干扰断/段码(字段式)LCD液晶显示驱动IC-VK2C22A/B,替代市面16C22,44*4/40*4点显示

    产品品牌:永嘉微电/VINKA 产品型号:VK2C22A/B 封装形式:LQFP52/48 产品年份:新年份 概述: VK2C22是一个点阵式存储映射的LCD驱动器,可支持最大176点(44SEGx4 ...

  10. ActiveMQ、RabbitMQ、RocketMQ、Kafka四种消息中间件分析介绍

    ActiveMQ.RabbitMQ.RocketMQ.Kafka四种消息中间件分析介绍 我们从四种消息中间件的介绍到基本使用,以及高可用,消息重复性,消息丢失,消息顺序性能方面进行分析介绍! 一.消息 ...