专栏首页Go语言指北Go高阶指南02,slice 实现原理

Go高阶指南02,slice 实现原理

slice 切片,因为其可以方便的进行扩容、传递等,在实际应用中比数组更加灵活。切片的一些基础使用可以看下之前的文章,传送门

这里我们再更加深层次的了解下 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 ,结构如下图:

切片从数组 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() 向切片追加元素时有可能触发扩容,扩容后将会生成新的切片

本文分享自微信公众号 - 微客鸟窝(gophpython),作者:有码无尘

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-08-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Go高阶指南04,struct 实现原理

    Go 语言中没有像类的概念,但是可以通过结构体 struct实现面向对象编程。struct 结构体的一些基础使用可以看下之前的文章--结构体和接口的使用,传送门...

    微客鸟窝
  • Go高阶指南05,iota 实现原理

    我们知道,iota 常用于 const 表达式中,它的值是从 0 开始,每增加一行,iota 值 +1。

    微客鸟窝
  • Go高阶指南06,string 实现原理

    源代码中 src/builtin/builtin.go string 的描述如下:

    微客鸟窝
  • Go高阶指南09,select 实现原理

    select 是 GO 语言中用来提供 IO 复用的机制,它可以检测多个 chan 是否 ready(可读/可写)。

    微客鸟窝
  • Go高阶指南10,一文搞懂 range 实现原理

    range 是 Go 语言用来遍历的一种方式,它可以操作数组、切片、map、channel 等。

    微客鸟窝
  • Go高阶指南07,一文搞懂 defer 实现原理

    defer 语句用于延迟函数的调用,使用 defer 关键字修饰一个函数,会将这个函数压入栈中,当函数返回时,再把栈中函数取出执行。

    微客鸟窝
  • Go高阶指南14,内存的分配原理

    Go 中实现的内存分配器,简单的说就是维护了一大块全局内存,每个线程(Go 中的 P)维护一小块的私有内存,当私有内存不足时再向全局申请。内存分配与 GC(垃圾...

    微客鸟窝
  • go踩坑指南:修改slice里的struct类型的元素不生效?

    slice数据结构里,包含指向底层array的指针。slice作为参数传递时,实际是创建了一个新的slice对应,同时将原slice对应的array指针copy...

    hugo_lei
  • PHP转Go速学手册

    整理了一份简要的手册,帮助大家高效的上手Go语言,主要是通过对比PHP和Go的不同点来强化理解,内容主要分为以下四部分:

    用户1093396
  • 深度解密Go语言之unsafe

    个人认为,学习本身并不是一件轻松愉快的事情,寓教于乐是个美好的愿望。想要深刻地领悟,就得付出别人看不见的努力。学习从来都不会是一件轻松的事情,枯燥是正常的。耐住...

    梦醒人间
  • Go基础之--数组和切片

    数组 数组的定义: 数组是具有固定长度并拥有零个或者多个相同数据类型元素的序列 定义一个数组的方法: var 变量名[len] type 例子: var a[5...

    coders
  • Go Code Review Comments 译文(截止2018年7月27日)

    持续更新中… 原文最新链接 https://github.com/golang/go/wiki/CodeReviewComments/5a40ba36d38...

    潘成涛
  • Go 垃圾回收

    Garbage Collection( GC )也就是垃圾回收到底是什么?内存空间是有限的,诸如变量等需要分配内存才能存储数据,而当这个变量不再使用的时候就需要...

    凌虚
  • Go 问答汇总篇 二

    继上篇 Go 问答汇总,已经过去了一个多月。今天汇总下近一个多月我关于 Go 的回答。

    波罗学
  • Json序列化在golang中的应用

    golang对json序列化和反序列化的操作实在是难受,所以说用习惯了高级语言特性,再转到这些偏原生的写法上就会很难受。

    happlyfox
  • Go 语言学习之 slice

    slice 是有 3 个字段的数据结构,这些数据结构包含 Go 语言需要操作底层数组的元数据,这 3 个字段分别是指向底层数组的指针,切片的长度和切片的容量。

    frank.
  • Golang内存逃逸是什么?怎么避免内存逃逸?

    C/C++中动态分配的内存需要我们手动释放,导致猿们平时在写程序时,如履薄冰。这样做有他的好处:程序员可以完全掌控内存。但是缺点也是很多的:经常出现忘记释放内存...

    sunsky
  • Golang常见的坑笔记

    Format 的时候 时间必须是 2006-01-02 15:04:05 ,奇葩时间。

    solate
  • 【译】golang 可变参数函数终极指南

    Ultimate Guide to Go Variadic Functions 原文地址 https://blog.learngoprogramming.com...

    goodspeed

扫码关注云+社区

领取腾讯云代金券