[go]Slice 切片原理

2023年 9月 25日 59.5k 0

go 切片

在go 语言的世界中,切片是一个很常用的数据结构,同时也有很多的坑,在面试中十个有八个面试官会问到,本文主要整理下切片数据结构、创建方式、扩容方式、常见面试题等。

slice 又称动态数组,依托数组实现,可以方便地进行扩容、传递等,实际使用中比数组更加灵活。

切片与底层数组共享一个内存地址,当切片的值改变时,会影响底层数组的值也一起改变,当切片扩容时会重新申请内存地址,此时切片不会影响底层数组

slice 依托数组实现,底层数组对用户屏蔽,在底层数组容量不足时可以实现自动重新分配并生成新的slice。

数据结构

type slice struct {
    array unsafe.Pointer    // 指向底层数组的地址
    len int                 // 长度  
    cap int                 // 容量
}

创建切片

  • 使用make

使用make 创建slice ,可以指定长度和容量,创建时底层会分配一个数组,数组的长度即容量
例如slice :=make([]int,5,10),底层指向的就是一个长度为10的数组,该slice 长度为5 ,可以使用slice[0]~slice[4]来操作元素,capacity为10,表示后续想slice添加新元素时不必重新分配内存,直接使用预留内存

  • 使用数组创建

使用数组创建slice ,slice与原数组共用一部分内存
slice:=array[5:7]切片从数组array[5]开始到array[7]结束,切片长度为2,数组后面的内容都作为切片的预留内存

slice 扩容

使用append 想slice 追加元素时,如果slice 空间不足,将会触发slice 扩容,扩容实际上就是重新分配一个更大的内存,将原slice 的数据拷贝进新的slice ,然后返回新的slice
扩容原则

  • 如果原Slice容量小于1024,则新Slice容量将扩大为原来的2倍
  • 如果原slice 大于等于1024 扩大为原来的1.25倍

append 的实现步骤

  • 假如Slice容量够用,则将新元素追加进去,Slice.len++,返回原Slice
  • 原Slice容量不够,则将Slice先扩容,扩容后得到新Slice
  • 将新元素追加进新Slice,Slice.len++,返回新的Slice。

slice copy

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

例如长度为10的切片拷贝到长度为5的切片时,将会拷贝5个元素,也就是说copy 的过程中不会发生扩容
// 可以使用这个特性来比较两个数的大小

min= copy(make([]struct{},a),make([]struct{},b))

特殊切片

根据数组或者切片生成新切片一般使用slice:=array[start:end],这种新生成的切片没有指定容量,实际上新切片容量为从start 开始到array 结束
比如如下两个切片,长度和容量都是一致的,使用共同的内存地址

sliceA := make([]int,5,10)
sliceB := sliceA[0:5]

根据数组或者切片生成新切片还有另一种写法,可以指定新切片的容量slice := array[start:end:cap],cap不能超过原切片的实际值

sliceA := make([]int,5,10)
sliceB := sliceA[0:5]
sliceC := sliceA[0:5:5]

总结

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

面试题

package main

import "fmt"

func main() {

	{
		test := []int{1, 2, 3, 4, 5}
		change2(test)
		fmt.Println(test)
	}
}

func change2(param []int) {
	param = append(param, 6)
}

输出结果为 1,2,3,4,5
解析:change2 对切片进行了扩容操作,param 是一个函数内的局部变量已经指向了新的内存地址,test指向的底层数组不变

package main

import "fmt"

func main() {
	{
		test := []int{1, 2, 3, 4, 5}
		change(test)
		fmt.Println(test)

	}

}

func change(param []int) {
	param = append(param[:1], param[2:]...)
}

输出结果为 1,3,4,5,5
解析,底层数组没变,append 操作把 切片前4位的值覆盖掉了

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论