在Go
中所说的空结构体就是struct{}
,它是一种特殊的存在,可能你在项目中看到过,但并没有深入的了解它的应用场景,这里结合自己平时项目中的经验,介绍一下空结构体(struct{})的一些应用场景。
Go
语言中的空结构体(struct{})是一种零内存占用的特殊类型,其所有实例可能共享同一内存地址(zerobase),它不包含任何字段,但却有很多应用场景。
紧接上文,这里就来验证一下,空结构体的内存地址是否相同,以及内存占用大小:
package main
import (
"fmt"
"unsafe"
)
type Empty struct{}
func main() {
var e Empty
var s struct{}
fmt.Printf("e addr: %p, size: %d\n", &e, unsafe.Sizeof(e)) // output: e addr: 0x5800a0, size: 0
fmt.Printf("s addr: %p, size: %d\n", &s, unsafe.Sizeof(s)) // output: s addr: 0x5800a0, size: 0
}
可以看出,空结构体占用内存的字节数是0
,所以说它不占内存空间。
再者,两个空结构体的内存地址是相同的,但是不是所有空结构体
的内存地址都是相同的呢?不一定。
在Go
语言规范中有这么一段话:
A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory.
意思就是说,如果结构体或数组类型不包含大小大于零的字段(或元素),则其大小为零。两个不同的零大小变量可能在内存中具有相同的地址。
注意,这里说是可能,也就是说也可能不相同,如果发生了内存逃逸
,那么它们的地址就不一定相同。
下面就列举一些空结构体
的一些应用实践。
标记或计数
空结构体可以用来标记一个元素是否存在,或者记录出现次数,或者去重等。
func ReDupUint64(ui64s []uint64) []uint64 {
var rets []uint64
m := make(map[uint64]struct{})
for _, ui64 := range ui64s {
if _, ok := m[ui64]; ok {
continue
}
rets = append(rets, ui64)
m[ui64] = struct{}{}
}
return rets
}
这是一个用来标记切片中已经存在元素并去重的用法,因为空结构体不占用空间,用来作为map
的值来占位
非常合适。
实现轻量级集合(Set)
我们都知道,在Go
语言中没有集合(Set)类型,但我们使用map
来自定义一个集合,map
的 key 是集合的元素,能保证唯一,map
的 value 就使用空结构体(struct{})来占位(map[string]struct{}
),避免使用bool
存储,从而达到节省内存的作用。
type Set map[string]struct{}
func (s Set) Has(key string) bool {
_, ok := s[key]
return ok
}
func (s Set) Add(key string) {
s[key] = struct{}{}
}
通道信号通知
在并发编程中,通过空结构体(struct{})通道传递信号,因为仅仅是传递信号而已,不需要关注数据内容,使用空结构体占位也能起到节省内存的作用。
done := make(chan struct{})
go func() {
// 执行任务
done <- struct{}{} // 发送完成信号
}()
<-done // 阻塞等待信号
无状态方法接收器
当结构体不需要定义任何字段,用它来实现接口时,只需要定义接收器即可。
type Logger struct{} // 空结构体
func (l Logger) Log(msg string) {
fmt.Println(msg) // 不依赖实例状态
}
// 调用时零内存开销
logger := Logger{}
logger.Log("info")
优化结构体内存布局
在之前的文章中,有说到空结构体作为结构体字段置于末尾时会产生内存对齐填充,那么我们可以前置空结构体字段,不占用空间。
type A struct {
_ struct{} // 零大小
n int64 // 偏移量0
}
最后
空结构体
在 Go 语言中,看似不起眼的东西,但它的用途不可小觑。
在项目中,合理且巧妙的应用空结构体,从而达到内存空间的零占用
,但也不要过度用空结构体而降低了代码的可读性。