聊一聊go语言的空值

写在文章开头

这篇文章会通过unsafe api获取go语言不同类型大小,深入底层分析go语言中如何完成空值内存分配和空值的应用场景。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。

获取类型的大小

我们以整数类型为例,通过unsafe包的Sizeof函数方法即可得到当前整数类型字节数,关于这个函数学习过C语言的同学都知道C语言中的sizeof方法,而unsafeSizeof本质就是对该方法的一层封装:

css 复制代码
 num := 1
 fmt.Println("num size:", unsafe.Sizeof(num))

对应输出结果如下,因为笔者使用的操作系统为64位,而整数类型和字长是和操作系统位数保持一致,所以num的字节数为8字节(64bit):

arduino 复制代码
num size: 8

同理我们这里给出一个结构体并打印其大小:

go 复制代码
type people struct {
 id   int
 name string
}

func main() {

 p := people{1, "xiaoming"}
 fmt.Println("people size:", unsafe.Sizeof(p))
}

输出结果为24

arduino 复制代码
people size: 24

其原因是字符串变量name 底层有指向字符的指针和记录容量大小的int,二者都是8字节,再加上int的8字节总共24字节:

指针类型的长度

同理我们再查看不同的指针类型的长度:

csharp 复制代码
func main() {
 var n = 1
 var f = 1.0
 fmt.Println("num ptr size:", unsafe.Sizeof(&n))
 fmt.Println("float ptr size:", unsafe.Sizeof(&f))
}

从输出结果来看,对于不同的类型,go语言对应指针大小都是8字节

arduino 复制代码
num ptr size: 8
float ptr size: 8

原因也是因为笔者操作系统为64位的缘故,指针指向的地址永远保持在64位以内:

空基本类型和空结构体的大小

我们再来看一些有意思的,假如我们声明的基本类型没有赋值,那么这个变量占用的内存空间是多少呢?

csharp 复制代码
func main() {
 var num int
 fmt.Println("num size:", unsafe.Sizeof(num))
}

还是8个字节,很明显go语言对于基本类型的内存分配如论是否赋值对应内存分配的大小都是固定的:

arduino 复制代码
num size: 8

那要是一个空结构体呢?

csharp 复制代码
type emptyObj struct {
}

func main() {
 var e emptyObj
 fmt.Println("emptyObj size:", unsafe.Sizeof(e))
}

输出结果为0,不难猜出因为自定义类型的原因,go语言在编译期会检查该类型是否有内置变量从而动态分配内存大小:

arduino 复制代码
emptyObj size: 0

go语言空值更进一步的理解

那么问题来了,既然空结构体的大小为0,如果考虑到空结构体0大小的特点,我们是否可以认为如果这些空结构体存在地址时,它们的地址都是一样的呢?

对此我们给出下面这段代码:

go 复制代码
type emptyObj struct {
}

func main() {

 e := emptyObj{}
 e2 := emptyObj{}
 fmt.Printf("%p\n", &e)
 fmt.Printf("%p\n", &e2)
}

从输出结果来看,两个不同的变量创建的空结构体指向的内存地址都是一样的:

复制代码
0x10ca478
0x10ca478

关于这个地址值,我们可以在内存分配的工具包malloc.go看到这样一个变量,这就是go语言中对于空结构体都采用zerobase的指针地址,这样做是得所有空结构体都复用一个指针,确保所有的空结构体都能用同一块内存使用以节约宝贵的内存:

这一点我们可以在源码中得以印证:

csharp 复制代码
// base address for all 0-byte allocations
var zerobase uintptr



func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
 if gcphase == _GCmarktermination {
  throw("mallocgc called with gcphase == _GCmarktermination")
 }
 //如果size为0则分配zerobase的指针地址
 if size == 0 {
  return unsafe.Pointer(&zerobase)
 }
}

空结构体的应用场景

go语言为了简而精,它没有做到像Java那样定义了各种数据结构,所以为了实现hashset这样的数据结构,我们只能自行实现,了解Java的Hashset的读者都知道,Hashset本质就是对hashMap的封装,用map的key作为set的value,将value全部设置为null。

同样的利用空结构体这一特点,我们通过go语言map创建一个stringkey,接口为value的变量,赋值时将需要记录的value作为mapkey,而value全部用空结构体让这些结构体都是用zerobase以避免没必要的内存空间占用:

c 复制代码
 set := map[string]interface{}{}
 set["key1"] = struct{}{}
 set["key2"] = struct{}{}

同理go语言在进行协程并发工作时常用到channel发送信号,有时候我们只是为了发送信号,信号没有任何含义,为了节约宝贵的内存,我们同样可以使用空结构体作为channel以确保在可以发送信号的情况下节约内存空间:

go 复制代码
func main() {
 //创建计时门闩
 var wg sync.WaitGroup
 wg.Add(4)

 //创建空结构体
 c := make(chan interface{})

 //创建协程发送空结构体信号
 go func() {
  for i := 0; i < 2; i++ {
   time.Sleep(3000)
   c <- struct{}{}
   wg.Done()
  }

 }()

 //协程2等待空结构体信号并按下倒计时门闩
 go func() {
  for i := 0; i < 2; i++ {
   <-c
   fmt.Printf("%p\r\n", &c)
   wg.Done()
  }

 }()

 //结束并输出结果
 wg.Wait()
 fmt.Println("结束")
}

可以看到所有的空结构体信号都是用到zerobase的指针的地址:

复制代码
0xc0000ca018
0xc0000ca018
结束 

小结

以上便是笔者对于go语言空值的介绍,不难看出go语言为了压榨服务器的性能在内存分配方面也是做到极致!

我是 sharkchiliCSDN Java 领域博客专家开源项目---JavaGuide contributor ,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。

相关推荐
苍何4 小时前
字节发力,豆包大模型2.0 震撼来袭(附 Trae 实测)
后端
苍何4 小时前
不会剪辑的人,开始用 AI 批量出爆款了
后端
苍何4 小时前
百度 APP 正式接入 OpenClaw,所有人限时免费!
后端
Volunteer Technology5 小时前
DynamicTP动态线程池(四)
java·spring boot·后端·spring
野犬寒鸦5 小时前
从零起步学习并发编程 || 第九章:Future 类详解及CompletableFuture 类在项目实战中的应用
java·开发语言·jvm·数据库·后端·学习
uzong6 小时前
软件工程师应该尽量改掉的坏习惯
后端
高山上有一只小老虎7 小时前
SpringBoot项目单元测试
spring boot·后端·单元测试
❀͜͡傀儡师7 小时前
Spring Boot Pf4j模块化能力设计思考
运维·spring boot·后端·pf4j
星空彼岸0079 小时前
SA-Token在SpringBoot中的实战指南
java·spring boot·后端
树獭叔叔10 小时前
大模型行为塑造:SFT 与 LoRA 深度解析
后端·aigc·openai