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. hyperlpr centos 使用记录

    1.下载最新版python3.7 Anacondawget https://repo.anaconda.com/archive/Anaconda3-5.3.1-Linux-x86_64.sh 2.安装 ...

  2. php 二维数组转换一维数组

    $result = array_reduce($res, function ($result, $value) { return array_merge($result, array_values($ ...

  3. 【RocketMQ】NameServer的启动

    NameServer是一个注册中心,Broker在启动时向所有的NameServer注册,生产者Producer和消费者Consumer可以从NameServer中获取所有注册的Broker列表,并从 ...

  4. UiPath培训教程

    匠厂出品,必属精品   Uipath中文社区qq交流群:465630324 uipath中文交流社区:https://uipathbbs.comRPA之家qq群:465620839 第一课--UiPa ...

  5. Halcon · 曲线宽度检测算法总结

    视觉检测中,直线的宽度很好检测,即两条平行线的垂直距离,而曲线的宽度检测则需要另辟蹊径. 检测图像中曲线边缘的宽度,用以判断边缘是否崩缺,总结如下五种方法: 1.图像匹配判断 概述:建立标准图像参考, ...

  6. IDEA快速创建maven项目

    遇到问题不要急,不要怕. 一.  二. 三.  四.Finish进来之后,项目会加载一会,之后会是下面这样子.  五.继续往下面配置,建立java和resorces文件夹  六.下面配置tomcat服 ...

  7. 一文详解|Go 分布式链路追踪实现原理

    在分布式.微服务架构下,应用一个请求往往贯穿多个分布式服务,这给应用的故障排查.性能优化带来新的挑战.分布式链路追踪作为解决分布式应用可观测问题的重要技术,愈发成为分布式应用不可缺少的基础设施.本文将 ...

  8. 以脚本形式运行python库

    技术背景 当我们尝试运行python的帮助文档时,会看到如下这样的一个说明: $ python3 -h usage: python3 [option] ... [-c cmd | -m mod | f ...

  9. 综合案例_文件搜索和FileFilter过滤器的原理和使用

    文件搜索 需求 : 遍历D:\aaa文件夹,及 aaa 文件夹的子文件夹并且只要.java结尾的文件 分析: 1.目录搜索,无法判断多少级目录,所以使用递归,遍历所有目录 2.遍历目录时,获取的子文件 ...

  10. 对象数组和对象对象数组的for-each循环

    对象数组的声明 类名称 对象数组名[] = null: 对象数组名 = new 类名称[长度] 定义并开辟数组 类名称 对象数据名[] = new 类名称[长度]; 在声明一个对象数组后,必须对每个数 ...