Go语言slice 实现原理,终于搞清楚它的扩容机制了!

简介: slice 切片,因为其可以方便的进行扩容、传递等,在实际应用中比数组更加灵活。
  1. 以下程序输出什么?
package main
import (
  "fmt"
)
func main() {
  var array [20]int
  var slice = array[10:11]
  fmt.Println("lenth: ", len(slice))
  fmt.Println("capacity: ", cap(slice))
  fmt.Println(&slice[0] == &array[10])
}
复制代码

是:

lenth:  1
capacity:  10
true
复制代码

slice 根据数组array创建,与数组共享存储空间,slice起始位置是array[5],长度为1,容量为5, slice[0]和array[5]地址相同。

  1. 以下程序输出什么?
package main
import (
 "fmt"
)
func main() {
 orderLen := 5
 order := make([]uint16, 2 * orderLen)
 pollorder := order[:orderLen:orderLen]
 lockorder := order[orderLen:][:orderLen:orderLen]
 fmt.Println("len(pollorder) = ", len(pollorder))
 fmt.Println("cap(pollorder) = ", cap(pollorder))
 fmt.Println("len(lockorder) = ", len(lockorder))
 fmt.Println("cap(lockorder) = ", cap(lockorder))
}
复制代码

是:

len(pollorder) =  5
cap(pollorder) =  5
len(lockorder) =  5
cap(lockorder) =  5
复制代码
  • order[low:high:max]意思是对 order 进行切片,新切片范围是[low, high),新切片容量是 max;
  • order 长度为2倍的 orderLen;
  • pollorder 切片是 order 的前半部分切片;
  • lockorder 是 order 的后半部分切片,即原 order 分成了两段;
  • 所以,pollorder 和 lockerorder 的长度和容量都是 orderLen,即5。

slice 实现原理

源码包 src/runtime/slice.go 定义的 slice 数据结构为:

type slice struct {
  array unsafe.Pointer
  len   int
  cap   int
}
复制代码
  • array 指针指向底层数组
  • len 表示切片长度
  • cap 表示切片容量

make 创建 slice

make 创建 slice 可以同时指定其长度和容量,底层会分配一个数组,数组的长度即容量。

slice = make([]int,5,10): 表示改 slice 长度为5,容量为10。

数组创建 slice

使用数组来创建 slice 时,slice 与原数组共用一部分内存。

slice := array[5:7] 所创建的 slice ,结构如下图:

image.png

切片从数组 array[5] 开始,到数组 array[7] 结束(不包含array[7]),切片长度为 2,数组后面的内容作为切片的预留内存,即容量为5。

slice 扩容

在使用 append 向 slice 追加元素时,若 slice 空间不足则会发生扩容,扩容会重新分配一块更大的内存,将原 slice 拷贝到新 slice ,然后返回新 slice。扩容后再将数据追加进去。

扩容操作只只对容量,扩容后的 slice 长度不变,容量变化规则如下:

  • 若 slice 容量够用,则将新元素追加进去,slice.len++,返回原 slice
  • 若 slice 容量不够用,将 slice 先扩容,扩容得到新 slice,将新元素追加进新 slice,slice.len++,返回新 slice。

slice copy

使用 copy() 内置函数拷贝两个切片时,会将源切片的数据逐个拷贝到目的的切片指向的数组中,拷贝数量取两个切片长度的最小值

例如将长度为10的切片拷贝到长度为5的切片时,将会拷贝5个元素。也就是说 copy 不会发生扩容

  1. 根据数组或切片来生成新的切片一般使用 slice := array[start:end] 方式,这种新生成的切片没有指定容量,新切片的容量是从 start 开始到 array 的结束(注意并不是到 end)。
  2. 另一种写法,生成新切片同时指定其容量:slice[start:end:cap] ,其中的 cap 为新切片的容量,容量不能超过原切片实际值。

总结

  • 创建切片时可根据实际需要预分配容量,尽量避免追加过程中扩容操作,有利于提升性能
  • 切片拷贝时需要判断实际拷贝的元素个数
  • 谨慎使用多个切片操作同一个数组,以防读写冲突
  • 每个切片都指向一个底层数组
  • 每个切片都保存了当前切片的长度、底层数组可用容量
  • 使用len()、cap()计算切片长度、容量时,时间复杂度均为O(1),不需要遍历切片
  • 通过函数传递切片时,不会拷贝整个切片,因为切片本身只是个结构体而矣
  • 使用 append() 向切片追加元素时有可能触发扩容,扩容后将会生成新的切片


相关文章
|
6月前
|
Go 开发者
Go语言内存共享与扩容机制 -《Go语言实战指南》
本文深入探讨了Go语言中切片的内存共享机制与自动扩容策略。切片作为动态数组的抽象,其底层结构包含指针、长度和容量。多个切片可能共享同一底层数组,修改一个切片可能影响其他切片。当切片容量不足时,`append`会触发扩容,新容量按指数增长以优化性能。为避免共享导致的副作用,可通过`copy`创建独立副本或在函数中使用只读方式处理。最后总结了最佳实践,帮助开发者高效使用切片,写出更优代码。
174 10
|
6月前
|
Go 调度
GO语言函数的内部运行机制分析
以上就是Go语言中函数的内部运行机制的概述,展示了函数在Go语言编程中如何发挥作用,以及Go如何使用简洁高效的设计,使得代码更简单,更有逻辑性,更易于理解和维护。尽管这些内容深入了一些底层的概念,但我希望通过这种方式,将这些理论知识更生动、更形象地带给你,让你在理解的同时找到编程的乐趣。
101 5
|
6月前
|
安全 Go
defer关键字:延迟调用机制-《Go语言实战指南》
Go 语言中的 `defer` 是用于延迟执行函数调用的关键字,广泛应用于资源释放、异常捕获和日志记录等场景。它在函数返回前执行,支持栈式后进先出(LIFO)顺序,参数求值时机为声明时而非执行时。常见用法包括文件关闭、锁解锁及结合 `recover` 处理 panic。尽管高效,频繁使用可能带来性能开销,需谨慎处理。总结而言,`defer` 是构建健壮代码的核心工具之一。
|
6月前
|
人工智能 安全 Go
Go语言中 Mutex 的实现原理
本文深入解析了 Go 中 `sync.Mutex` 的实现原理及其工作机制。`Mutex`(互斥锁)是并发编程中的基础同步机制,用于保护共享资源不被多个 Goroutine 同时访问。Go 的 `sync.Mutex` 通过轻量级的结构体实现,包含 `state` 和 `sema` 两个字段,分别用于表示锁状态和管理 Goroutine 的阻塞与唤醒。 文章详细分析了锁的获取(`Lock()`)和释放(`Unlock()`)过程,包括快速路径和慢速路径的实现逻辑。在慢速路径中,介绍了自旋锁优化、饥饿模式以及信号量机制的应用,确保高效且公平地管理锁竞争。
146 6
|
6月前
|
人工智能 Go
[go]Slice 切片原理
本文详细介绍了Go语言中的切片(slice)数据结构,包括其定义、创建方式、扩容机制及常见操作。切片是一种动态数组,依托底层数组实现,具有灵活的扩容和传递特性。文章解析了切片的内部结构(包含指向底层数组的指针、长度和容量),并探讨了通过`make`创建切片、基于数组生成切片以及切片扩容的规则。此外,还分析了`append`函数的工作原理及其可能引发的扩容问题,以及切片拷贝时需要注意的细节。最后,通过典型面试题深入讲解了切片在函数间传递时的行为特点,帮助读者更好地理解和使用Go语言中的切片。
149 0
|
7月前
|
存储 算法 物联网
解析局域网内控制电脑机制:基于 Go 语言链表算法的隐秘通信技术探究
数字化办公与物联网蓬勃发展的时代背景下,局域网内计算机控制已成为提升工作效率、达成设备协同管理的重要途径。无论是企业远程办公时的设备统一调度,还是智能家居系统中多设备间的联动控制,高效的数据传输与管理机制均构成实现局域网内计算机控制功能的核心要素。本文将深入探究 Go 语言中的链表数据结构,剖析其在局域网内计算机控制过程中,如何达成数据的有序存储与高效传输,并通过完整的 Go 语言代码示例展示其应用流程。
137 0
|
分布式计算 安全 Java
简单易懂的 Go 泛型使用和实现原理介绍
简单易懂的 Go 泛型使用和实现原理介绍
|
12月前
|
存储 安全 Go
Go 语言以其高效的并发编程能力著称,主要依赖于 goroutines 和 channels 两大核心机制
Go 语言以其高效的并发编程能力著称,主要依赖于 goroutines 和 channels 两大核心机制。本文介绍了这两者的概念、用法及如何结合使用,实现任务的高效并发执行与数据的安全传递,强调了并发编程中的注意事项,旨在帮助开发者更好地掌握 Go 语言的并发编程技巧。
146 2
|
存储 算法 Java
Go语言的内存管理机制
【10月更文挑战第25天】Go语言的内存管理机制
204 2
|
存储 编译器 Go