Golang Slice原理剖析
本文最后更新于20 天前,其中的信息可能已经过时,如有错误请留言

slice(切片)是建立在数组之上的一个抽象类型。

一、切片的底层

它是一个标准库中定义的结构体,其定义如下:

type slice struct {​
    // 底层数组指针(或者说是指向一块连续内存空间的起点)
​    array unsafe.Pointer
​    // 长度​    len   int
​    // 容量​    cap   int​
}

切片扩容

1、如果原Slice容量小于1024,则新Slice容量将扩大为原来的2倍;

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

3、如果扩容后的大小仍不能满足,那么直接扩容到所需的容量

4、最终还需要按照go内存管理的级别去对齐内存

二、切片相关操作

1、新增元素(append)

问题:append操作得到新的slice与原来的 slice是什么关系?

如果append导致slice发生扩容,则新的slice和原来的slice指向不同的内存;

如果append没有导致slice发生扩容,则新的slice和原来的slice指向同一块内存;

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	s1 := make([]int, 2)
	s2 := append(s1, 3)
	PrintSliceStruct(&s1)
	PrintSliceStruct(&s2)
	s3 := append(s2, 4)
	PrintSliceStruct(&s3)
}

func PrintSliceStruct(s *[]int) {
	ss := (*reflect.SliceHeader)(unsafe.Pointer(s))
	fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}

输出如下:

slice struct: &{Data:824634794160 Len:2 Cap:2}, slice is &[0 0]
slice struct: &{Data:824634843200 Len:3 Cap:4}, slice is &[0 0 3]
slice struct: &{Data:824634843200 Len:4 Cap:4}, slice is &[0 0 3 4]

2、截取元素

问题:截取操作得到新的slice与原来的 slice是什么关系?

截取操作得到的新的slice与原来的slice指向同一块内存

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	s := make([]int, 5)
	s1 := s[1:]
	PrintSliceStruct(&s1)
	s2 := s[1:3]
	PrintSliceStruct(&s2)
	s3 := s[len(s)-1:]
	PrintSliceStruct(&s3)
	s4 := s[2:]
	PrintSliceStruct(&s4)
}

func PrintSliceStruct(s *[]int) {
	ss := (*reflect.SliceHeader)(unsafe.Pointer(s))
	fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}

输出如下:

slice struct: &{Data:824633770712 Len:4 Cap:4}, slice is &[0 0 0 0]
slice struct: &{Data:824633770712 Len:2 Cap:4}, slice is &[0 0]
slice struct: &{Data:824633770736 Len:1 Cap:1}, slice is &[0]
slice struct: &{Data:824633770720 Len:3 Cap:3}, slice is &[0 0 0]

3、删除元素

问题:删除操作得到新的slice与原来的 slice是什么关系?

删除操作一般是通过append来完成的,请看以下案例:

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	s := []int{1, 2, 3, 4, 5}
	_ = s[4]
	PrintSliceStruct(&s)
	s1 := append(s[:1], s[2:]...)
	PrintSliceStruct(&s1)
	PrintSliceStruct(&s)
	_ = s[4]
	// 访问s1[4]会报错,因为s1的长度为4
	// _ = s1[4]
}

func PrintSliceStruct(s *[]int) {
	ss := (*reflect.SliceHeader)(unsafe.Pointer(s))
	fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}

输出如下:

slice struct: &{Data:824633770704 Len:5 Cap:5}, slice is &[1 2 3 4 5]
slice struct: &{Data:824633770704 Len:4 Cap:5}, slice is &[1 3 4 5]
slice struct: &{Data:824633770704 Len:5 Cap:5}, slice is &[1 3 4 5 5]

可以看到:

s和s1指向的是同一块内存

s1的长度变成了4,但s1的容量仍然是5,因为s[:1]的容量是5

4、深度拷贝

问题:深度拷贝得到的slice和原来的slice是什么关系?

深度拷贝得到的slice会把底层的数组复制,新的slice和旧的slice指向不同的内存

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	s1 := []int{1, 2, 3}
	s2 := make([]int, len(s1))
	copy(s2, s1)
	PrintSliceStruct(&s1)
	PrintSliceStruct(&s2)
}

func PrintSliceStruct(s *[]int) {
	ss := (*reflect.SliceHeader)(unsafe.Pointer(s))
	fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}

输出如下:

slice struct: &{Data:824633794752 Len:3 Cap:3}, slice is &[1 2 3]
slice struct: &{Data:824633794776 Len:3 Cap:3}, slice is &[1 2 3]

三、一些问题

1、切片通过函数,传的是什么?

传递的是slice结构体的拷贝

2、在函数内层改变切片,函数外层会受影响吗?

受不受影响,主要要看函数内层和函数外层的slice里面的指针指向的是否是同一块内存。如果切片在函数内层没有做扩容,那么指向就是原来的同一块内存,如果做了扩容,那么将指向不同的内存。

函数内层截取元素或者删除元素,对于函数外层是有影响的,因为截取或者删除,操作的和函数外层是同一块内存。

四、一道面试真题

package main

import (
	"fmt"
)

func main() {
	doAppend := func(s []int) {
		s = append(s, 1)
		printLengthAndCapacity(s)
	}
	s := make([]int, 8, 8)
	doAppend(s[:4])
	printLengthAndCapacity(s)
	doAppend(s)
	printLengthAndCapacity(s)
}

func printLengthAndCapacity(s []int) {
	fmt.Println(s)
	fmt.Printf("len=%d, cap=%d\n", len(s), cap(s))
}

输出结果如下:

[0 0 0 0 1]
len=5, cap=8
[0 0 0 0 1 0 0 0]
len=8, cap=8
[0 0 0 0 1 0 0 0 1]
len=9, cap=16
[0 0 0 0 1 0 0 0]
len=8, cap=8
感谢阅读!如有疑问请留言
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇