关于Go 空结构体的 3 种使用场景
作者:煎鱼eddycjy 发布时间:2024-05-22 17:45:21
目录
1、为什么使用
2、空结构体的特殊性
3、使用场景
3.1 实现方法接收者
3.2 实现集合类型
3.3 实现空通道
前言:
在 Go 语言中,有一个比较特殊的类型,经常会有刚接触 Go 的小伙伴问到,又或是不理解。
他就是 Go 里的空结构体(struct
)的使用,常常会有看到有人使用:
ch := make(chan struct{})
还清一色的使用结构体,也不用其他类型。高度常见,也就不是一个偶发现象了,肯定是背后必然有什么原因。
1、为什么使用
说白了,就是希望节省空间。但,新问题又来了,为什么不能用其他的类型来做?
这就涉及到在 Go
语言中 ”宽度“ 的概念,宽度描述了一个类型的实例所占用的存储空间的字节数。
宽度是一个类型的属性。在 Go 语言中的每个值都有一个类型,值的宽度由其类型定义,并且总是 8 bits
的倍数。
在 Go 语言中我们可以借助 unsafe.Sizeof
方法,来获取:
// Sizeof takes an expression x of any type and returns the size in bytes
// of a hypothetical variable v as if v was declared via var v = x.
// The size does not include any memory possibly referenced by x.
// For instance, if x is a slice, Sizeof returns the size of the slice
// descriptor, not the size of the memory referenced by the slice.
// The return value of Sizeof is a Go constant.
func Sizeof(x ArbitraryType) uintptr
该方法能够得到值的宽度,自然而然也就能知道其类型对应的宽度是多少了。
我们对应看看 Go 语言中几种常见的类型宽度大小:
func main() {
var a int
var b string
var c bool
var d [3]int32
var e []string
var f map[string]bool
fmt.Println(
unsafe.Sizeof(a),
unsafe.Sizeof(b),
unsafe.Sizeof(c),
unsafe.Sizeof(d),
unsafe.Sizeof(e),
unsafe.Sizeof(f),
)
}
输出结果:
8 16 1 12 24 8
你可以发现我们列举的几种类型,只是单纯声明,我们也啥没干,依然占据一定的宽度。
如果我们的场景,只是占位符,那怎么办,系统里的开销就这么白白浪费了?
2、空结构体的特殊性
空结构体在各类系统中频繁出现的原因之一,就是需要一个占位符。而恰恰好,Go 空结构体的宽度是特殊的。
如下:
func main() {
var s struct{}
fmt.Println(unsafe.Sizeof(s))
}
输出结果:
0
空结构体的宽度是很直接了当的 0,即便是变形处理:
type S struct {
A struct{}
B struct{}
}
func main() {
var s S
fmt.Println(unsafe.Sizeof(s))
}
其最终输出结果也是 0,完美切合人们对占位符的基本诉求,就是占着坑位,满足基本输入输出就好。
但这时候问题又出现了,为什么只有空结构会有这种特殊待遇,其他类型又不行?
这是 Go 编译器在内存分配时做的优化项
// base address for all 0-byte allocations
var zerobase uintptr
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
...
if size == 0 {
return unsafe.Pointer(&zerobase)
}
}
当发现 size
为 0 时,会直接返回变量 zerobase
的引用,该变量是所有 0 字节的基准地址,不占据任何宽度。
因此空结构体的广泛使用,是 Go 开发者们借助了这个小优化,达到了占位符的目的。
3、使用场景
了解清楚为什么空结构作为占位符使用的原因后,我们更进一步了解其真实的使用场景有哪些。
主要分为三块:
实现方法接收者。
实现集合类型。
实现空通道。
3.1 实现方法接收者
在业务场景下,我们需要将方法组合起来,代表其是一个 ”分组“ 的,便于后续拓展和维护。
但是如果我们使用:
type T string
func (s *T) Call()
又似乎有点不大友好,因为作为一个字符串类型,其本身会占据定的空间。
这种时候我们会采用空结构体的方式,这样也便于未来针对该类型进行公共字段等的增加。如下:
type T struct{}
func (s *T) Call() {
fmt.Println("脑子进煎鱼了")
}
func main() {
var s T
s.Call()
}
在该场景下,使用空结构体从多维度来考量是最合适的,易拓展,省空间,最结构化。
另外你会发现,其实你在日常开发中下意识就已经这么做了,你可以理解为设计模式和日常生活相结合的另类案例。
3.2 实现集合类型
在 Go 语言的标准库中并没有提供集合(Set
)的相关实现,因此一般在代码中我们图方便,会直接用 map
来替代。
但有个问题,就是集合类型的使用,只需要用到 key
(键),不需要 value
(值)。
这就是空结构体大战身手的场景了:
type Set map[string]struct{}
func (s Set) Append(k string) {
s[k] = struct{}{}
}
func (s Set) Remove(k string) {
delete(s, k)
}
func (s Set) Exist(k string) bool {
_, ok := s[k]
return ok
}
func main() {
set := Set{}
set.Append("煎鱼")
set.Append("咸鱼")
set.Append("蒸鱼")
set.Remove("煎鱼")
fmt.Println(set.Exist("煎鱼"))
}
空结构体作为占位符,不会额外增加不必要的内存开销,很方便的就是解决了。
3.3 实现空通道
在 Go channel
的使用场景中,常常会遇到通知型 channel
,其不需要发送任何数据,只是用于协调 Goroutine
的运行,用于流转各类状态或是控制并 * 况。
如下:
func main() {
ch := make(chan struct{})
go func() {
time.Sleep(1 * time.Second)
close(ch)
}()
fmt.Println("脑子好像进...")
<-ch
fmt.Println("煎鱼了!")
}
输出结果:
脑子好像进...
煎鱼了!
该程序会先输出 ”脑子好像进...“ 后,再睡眠一段时间再输出 "煎鱼了!",达到间断控制 channel
的效果。
由于该 channel
使用的是空结构体,因此也不会带来额外的内存开销。
来源:https://juejin.cn/post/7018037514246029349
猜你喜欢
- 最近几个不错网站被封,让人感觉很不爽,现在既不方便用,也不方便学习参考。正好想到曾经“截图”的事情,其实我认为互联网产品还有个特点,更新换代
- 今天写了个爬虫,在抓取数据的时候遇到一个问题,我觉得如果不注意,这个问题很容易被忽略,所以特意在博客记录下:问题描述:比如,我在提取信息时,
- 首先,我们需要着重介绍一些概念,以给你提供一些使这个“奇迹”得以发生的组成部分。太轻易地泄露伏笔对于讲故事来说不是个好的形式,所以那些不愿意
- 判断不仅包括电脑浏览器,还包括安卓、ios系统的手机以及平板电脑,游戏系统 var client = function(){ //呈现引擎
- 本文实例讲述了Python中Django框架利用url来控制登录的方法。分享给大家供大家参考。具体如下:from django.conf.u
- 我就废话不多说了,大家还是直接看代码吧!import pandas as pddef get_under_rolling(df,window
- 两周前,在给颜值在线的 flame 提交了几个 PR 之后,我将它封装成了容器,用于书签和在线应用的管理。但是在迁移个人
- Python是编译型语言还是解释型语言?回答这个问题前,应该先弄清楚什么是编译型语言,什么是解释型语言。所谓编译执行就是源代码经过编译器编译
- 下面介绍两种查看django 执行的sql语句的方法。方法一:queryset = Apple.objects.all()print que
- 前言上篇说到命令行执行测试用例的部分参数如何使用?今天将继续更新其他一些命令选项的使用,和pytest收集测试用例的规则!pytest执行用
- 理解切片基本用法:首先需要明白,可迭代对象,按照正数索引(正序)是从0开始的,按照负数索引(逆序)是从-1开始的。>>>
- mysql使用left join连接出现重复问题描述在使用连接查询的时候,例如以A表为主表,左连接B表,我们期望的是A表有多少条记录,查询结
- 最近有个部署需求,需要读取py文件格式的配置项,我的实现思路是把配置文件解析到内存中。主要使用两种方法:importlib.import_m
- 近段时间由于修改一个ASP程序(有SQL注入漏洞),在网上找了很多相关的一些防范办法,都不近人意,所以我将现在网上的一些方法综合改良了一下,
- Turtle库是Python语言中一个很流行的绘制图像的函数库,想象一个小乌龟,在一个横轴为x、纵轴为y的坐标系原点,(0,0)位置开始,它
- 俄罗斯方块,一个很有趣的一个小游戏,此次基于html+css+javaScript实现,包含在一个方块落地后自动生成方块、操控方块的移动以及
- Powerdesigner界面-tools-Resources-DBMS,点击左上角的New,选择copy from templete,如果
- goland leetcode 插件安装可以提高刷题效率,对于学习算法的同学是个不错的选择安装使用步骤:安装插件:a. 左上角Goland
- 目录前后端传输数据的编码格式Ajax提交urlencoded格式数据Ajax通过FormData上传文件Ajax提交Json格式数据Ajax
- Python之绘图和可视化1. 启用matplotlib最常用的Pylab模式的IPython(IPython --pylab)2. mat