0.前言
在 Go 语言中,空结构体 struct{}
是一个非常特殊的类型。它不包含任何字段,并且不占用任何内存空间。虽然乍一看似乎没什么用,但实际上,空结构体在 Go 编程中有着广泛的应用。本文将结合工作实例详细探讨空结构体的几种典型用法,并解释为什么它们在特定场景下非常有用。
1.特性
1.1 不占用内存空间
接下来我们来验证空结构体是否占用内存空间,并查找不同空结构体的内存地址是否一致。
代码:
输出:
由以上结果可知:
- 多个空结构体内存地址相同。
- 空结构体占用字节数为 0,即不占用内存空间。
- 多个空结构体值相等。
注意:多个空结构体内存地址 可能 相同,具体与 Go 编译器实现有关。
1.2 内存地址可能相同
1.1中我们验证空结构体的内存大小时,也验证了他们的内存地址,发现它们的内存地址一致,那么是不是巧合呢?
代码:
输出:
查阅相关文章和部分源码后,原因可能如下:
Go 语言的运行时环境中有一个名为 zerobase 的全局变量,它是一个 uintptr 类型,占用 8 个字节。每当分配的内存大小为 0 时,Go 语言会返回这个 zerobase 的地址,而不是分配新的内存空间1。因此,当你创建多个空结构体时,它们可能会返回相同的 zerobase 地址。
1.3 空结构体占用内存空间不为空的特例
空结构体也并不是什么时候都不会占用内存空间,比如空结构体作为另一个结构体字段时,根据位置不同,可能因内存对齐原因,导致外层结构体大小不一。
代码:
输出:
可以发现,当空结构体放在另一个结构体最后一个字段时,会触发内存对齐。如果你的程序对内存要求比较严格,则在使用空结构体作为字段时需要考虑这一点。
2. 用法
2.1 实现Go中的set类型:map+struct
空结构体最常用的地方,就是用来实现 set(集合) 类型了。Go 语言在语法层面没有提供 set 类型,但我们可以很方便地使用 map + struct{} 来实现 set 类型,
代码:
输出:
true
false
false
2.2 信号通知
空结构体另一个经常使用的方法是与 channel 结合当作信号来使用,示例如下:
输出:
waiting...
goroutine done
main exit
由于 struct{} 并不占用内存,所以实际上 channel 内部只需要将计数器加一即可,不涉及数据传输,故没有额外内存开销。
2.3 申请超大容量Slice
代码:
输出:
slice a size: 24
slice b size: 24
2.4 申请超大容量Array
基于空结构体不占用内存空间的特性,试创建个容量为 100万 的array。
代码:
输出:
array a size: 16000000
array b size: 0
输出可能会因系统架构和 Go 版本而异。
2.5 作为接口实现
代码:
输出:
写入数据前:Hello, World
!写入了 13 个字节
数据已被丢弃。
3 总结
空结构体 struct{} 在 Go 中虽小却有着巧妙的用途。从节省内存的角度看,它是表示空概念的理想选择。从语义上考虑,使用 struct{} 语义更明确,就是不关注值。由于内存对齐的影响,空结构体字段顺序可能影响外层结构体的大小,建议将空结构体放在外层结构体的第一个字段。无论是使用空结构体实现集合、信号通知和接口等,struct{} 都显示了其独特的价值。