Go 语言入门 3-动态数组(slice)的特性及实现原理

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

 

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

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

源码链接:

 

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

 

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 

 

源码地址:

 

针对 cap

针对 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

 

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

 

 

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

 

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

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

文章来源于互联网:Go 语言入门 3-动态数组(slice)的特性及实现原理

THE END
分享
二维码