揭秘 Go 语言中空结构体的强大用法

简介: Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。

0.前言

在 Go 语言中,空结构体 struct{} 是一个非常特殊的类型。它不包含任何字段,并且不占用任何内存空间。虽然乍一看似乎没什么用,但实际上,空结构体在 Go 编程中有着广泛的应用。本文将结合工作实例详细探讨空结构体的几种典型用法,并解释为什么它们在特定场景下非常有用。


1.特性

1.1 不占用内存空间

接下来我们来验证空结构体是否占用内存空间,并查找不同空结构体的内存地址是否一致。

代码:

QQ_1740625909307.png

输出:

QQ_1740625987970.png

由以上结果可知:

  1. 多个空结构体内存地址相同。
  2. 空结构体占用字节数为 0,即不占用内存空间。
  3. 多个空结构体值相等。

注意:多个空结构体内存地址 可能 相同,具体与 Go 编译器实现有关。

1.2 内存地址可能相同

1.1中我们验证空结构体的内存大小时,也验证了他们的内存地址,发现它们的内存地址一致,那么是不是巧合呢?


代码:

image.png

输出:

QQ_1740626453577.png

查阅相关文章和部分源码后,原因可能如下:

Go 语言的运行时环境中有一个名为 zerobase 的全局变量,它是一个 uintptr 类型,占用 8 个字节。每当分配的内存大小为 0 时,Go 语言会返回这个 zerobase 的地址,而不是分配新的内存空间1。因此,当你创建多个空结构体时,它们可能会返回相同的 zerobase 地址。

1.3 空结构体占用内存空间不为空的特例

空结构体也并不是什么时候都不会占用内存空间,比如空结构体作为另一个结构体字段时,根据位置不同,可能因内存对齐原因,导致外层结构体大小不一。

代码:

image.png

输出:


image.png


可以发现,当空结构体放在另一个结构体最后一个字段时,会触发内存对齐。如果你的程序对内存要求比较严格,则在使用空结构体作为字段时需要考虑这一点。


2. 用法

2.1 实现Go中的set类型:map+struct

空结构体最常用的地方,就是用来实现 set(集合) 类型了。Go 语言在语法层面没有提供 set 类型,但我们可以很方便地使用 map + struct{} 来实现 set 类型,

代码:

image.png

输出:

true

false

false

2.2 信号通知

空结构体另一个经常使用的方法是与 channel 结合当作信号来使用,示例如下:


image.png

输出:

waiting...

goroutine done

main exit

由于 struct{} 并不占用内存,所以实际上 channel 内部只需要将计数器加一即可,不涉及数据传输,故没有额外内存开销。

2.3 申请超大容量Slice

代码:

QQ_1740627891957.png

输出:

slice a size: 24

slice b size: 24


2.4 申请超大容量Array

基于空结构体不占用内存空间的特性,试创建个容量为 100万 的array。

代码:

QQ_1740627714232.png

输出:

array a size: 16000000

array b size: 0

输出可能会因系统架构和 Go 版本而异。

2.5 作为接口实现

代码:

image.png

输出:

写入数据前:Hello, World

!写入了 13 个字节

数据已被丢弃。

3 总结

空结构体 struct{} 在 Go 中虽小却有着巧妙的用途。从节省内存的角度看,它是表示空概念的理想选择。从语义上考虑,使用 struct{} 语义更明确,就是不关注值。由于内存对齐的影响,空结构体字段顺序可能影响外层结构体的大小,建议将空结构体放在外层结构体的第一个字段。无论是使用空结构体实现集合、信号通知和接口等,struct{} 都显示了其独特的价值。











引用:重新认识Golang中的空结构体


相关文章
|
10月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
458 2
|
Cloud Native 安全 Java
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
706 1
|
Cloud Native Go API
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
684 0
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
511 0
|
Cloud Native Java 中间件
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
535 0
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
1198 0
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
存储 缓存 安全
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
|
存储 负载均衡 监控
如何利用Go语言的高效性、并发支持、简洁性和跨平台性等优势,通过合理设计架构、实现负载均衡、构建容错机制、建立监控体系、优化数据存储及实施服务治理等步骤,打造稳定可靠的服务架构。
在数字化时代,构建高可靠性服务架构至关重要。本文探讨了如何利用Go语言的高效性、并发支持、简洁性和跨平台性等优势,通过合理设计架构、实现负载均衡、构建容错机制、建立监控体系、优化数据存储及实施服务治理等步骤,打造稳定可靠的服务架构。
597 1
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。

热门文章

最新文章