页首Html代码

返回顶部

golang 空切片和nil切片 有区别吗?

你看到的这个文章来自于http://www.cnblogs.com/ayanmw

判空

我们写程序都会有进行判空的操作:

对于map

对于golang的map,它为nil,你是无法直接使用的 如var oneMap map[uint32]uint32, 不赋值使用直接panic,必定要判空; 给它赋值为一个初始值var oneMap = map[uint32]uint32{}
使用的时候

if oneMap!=nil{//
  oneMap[1]=1
}

对于slice

初始化:

func Test_EmptySlice1(t *testing.T) {
	var emptySlice1 []int
	var emptySlice2 = []int{}
	if emptySlice1 == nil {
		t.Logf("emptySlice1==nil")
	}
	if emptySlice2 == nil {
		t.Logf("emptySlice2==nil")
	}
}

您会得到答案:

emptySlice1==nil

emptySlice2 就这么被忽略了, 这显然是不对的!

所以探索一下 slice 底层 空slice 和 nil slice的区别到底是什么?

探索runtime/slice.go 源码

slice的runtime源码为:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

这个结构体包含三个字段:
array:指向底层数组的指针。
len:切片的长度。
cap:切片的容量。

下面我们来详细探讨空切片和 nil 切片在源码层面的不同。

1. 空切片

空切片是指长度为 0 的切片,它可以通过 make 函数创建,也可以使用字面量创建。例如:

// 使用 make 函数创建
emptySlice1 := make([]int, 0)
// 使用字面量创建
emptySlice2 := []int{}

对于空切片,slice 结构体的各个字段值如下:

  • array:作为一个指针指向一个地址,但是这个地址是不存数据的。
  • len:值为 0,表示切片的长度为 0。
  • cap:值为 0,表示切片的容量为 0。

2. nil 切片

nil 切片是指未初始化的切片,它没有指向任何底层数组。例如:

var nilSlice []int

对于 nil 切片,slice 结构体的各个字段值如下:

  • array:值为 nil,表示没有指向任何底层数组。
  • len:值为 0,表示切片的长度为 0。
  • cap:值为 0,表示切片的容量为 0。

3. 源码层面的区别

在 Go 语言的源码中,空切片和 nil 切片的主要区别在于 array 字段的值。空切片的 array 字段指向一个空的底层数组,而 nil 切片的 array 字段为 nil。.

以下是一个简单的示例,展示了空切片和 nil 切片在内存中的不同:


func Test_EmptySlice(t *testing.T) {
	type UserShip struct {
		Id int
	}
	type Slice struct {
		Array unsafe.Pointer
		Len   int
		Cap   int
	}

	emptySlice := make([]*UserShip, 0)
	// 打印切片的长度、容量和底层数组的地址
	theVal := (*Slice)(unsafe.Pointer(&emptySlice))
	t.Logf("Length: %d, Capacity: %d, Array address: %p theVal=%#v , ptrIntVal=%v\n",
		len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal, *(*int)(theVal.Array))

	// 尝试向空切片添加元素
	newShip := &UserShip{}
	emptySlice = append(emptySlice, newShip)
	theVal = (*Slice)(unsafe.Pointer(&emptySlice))
	t.Logf("After append: Length: %d, Capacity: %d, Array address: %p theVal=%#v\n",
		len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal)
	emptySlice = append(emptySlice, newShip)
	theVal = (*Slice)(unsafe.Pointer(&emptySlice))
	t.Logf("After append: Length: %d, Capacity: %d, Array address: %p theVal=%#v\n",
		len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal)
	emptySlice = append(emptySlice, newShip)
	theVal = (*Slice)(unsafe.Pointer(&emptySlice))
	t.Logf("After append: Length: %d, Capacity: %d, Array address: %p theVal=%#v\n",
		len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal)
	{
		emptySlice2 := []uint32{}
		t.Logf("Length: %d, Capacity: %d, Array address: %p",
			len(emptySlice2), cap(emptySlice2), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice2)))
	}
	{
		emptySlice2 := []uint32{}
		t.Logf("Length: %d, Capacity: %d, Array address: %p",
			len(emptySlice2), cap(emptySlice2), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice2)))
	}
	{
		emptySlice2 := []uint32{}
		t.Logf("Length: %d, Capacity: %d, Array address: %p",
			len(emptySlice2), cap(emptySlice2), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice2)))
	}
}

输出结果:

=== RUN   Test_EmptySlice
    deepcopy_test.go:37: Length: 0, Capacity: 0, Array address: 0x16cb8c0 theVal=&common.Slice{Array:(unsafe.Pointer)(0x16cb8c0), Len:0, Cap:0} , ptrIntVal=0
    deepcopy_test.go:44: After append: Length: 1, Capacity: 1, Array address: 0xc0000a0000 theVal=&common.Slice{Array:(unsafe.Pointer)(0xc0000a0000), Len:1, Cap:1}
    deepcopy_test.go:48: After append: Length: 2, Capacity: 2, Array address: 0xc000052050 theVal=&common.Slice{Array:(unsafe.Pointer)(0xc000052050), Len:2, Cap:2}
    deepcopy_test.go:52: After append: Length: 3, Capacity: 4, Array address: 0xc0001206a0 theVal=&common.Slice{Array:(unsafe.Pointer)(0xc0001206a0), Len:3, Cap:4}
    deepcopy_test.go:56: Length: 0, Capacity: 0, Array address: 0x16cb8c0
    deepcopy_test.go:61: Length: 0, Capacity: 0, Array address: 0x16cb8c0
    deepcopy_test.go:66: Length: 0, Capacity: 0, Array address: 0x16cb8c0
--- PASS: Test_EmptySlice (0.00s)

可以看到 无论nil还是 empty 的slice , 在赋值后,len长度发生变化后, 它的 array值都会发生变化.

且 多个 empty slice 它们的 array值 都是: 0x16cb8c0 ,这个值打印出来ptrIntVal=0,这个值大概率就是一个全局变量,值为0.

这一定是golang的优化, 一个 实例化的emptySlice ,大小为0, 应该是一个0数组的地址,简化就是一个 0值指针的变量地址. 虽然emptySlice array 有值, 但是 它 不包含任何元素。

实际影响

虽然空切片和 nil 切片在长度和容量上都是 0,但它们在某些情况下的行为是不同的。例如,在 JSON 编码时,空切片会被编码为 [],而 nil 切片会被编码为 null。

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    // 创建空切片
    emptySlice := make([]int, 0)
    // 创建 nil 切片
    var nilSlice []int

    // 对空切片进行 JSON 编码
    emptyJSON, _ := json.Marshal(emptySlice)
    // 对 nil 切片进行 JSON 编码
    nilJSON, _ := json.Marshal(nilSlice)

    fmt.Printf("Empty slice JSON: %s\n", string(emptyJSON))
    fmt.Printf("Nil slice JSON: %s\n", string(nilJSON))
}

输出结果

Empty slice JSON: []
Nil slice JSON: null

综上所述,空切片和 nil 切片在源码层面的主要区别在于 array 字段的值,这也导致了它们在某些情况下的行为不同。

转载请注明出处:http://www.cnblogs.com/ayanmw 我会很高兴的!

posted @   ayanmw  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2012-02-28 C语言零碎记录之extern
2012-02-28 Linux零碎记录之ulimit【堆栈大小、stack size、进程数限制、文件句柄限制、linux用户空间限制】

页脚Html代码

点击右上角即可分享
微信分享提示