这是我参与「第三届青训营 -后端场」笔记创作活动的的第二篇笔记
前言:
Go语言中的结构体可以说是十分强大的数据结构,可以用来实现oop的思想,而Go中的结构体使用也是十分的讲究,接下来我们一探究竟。
空结构体及其作用
在Go中,一个有趣的现象是空结构体竟然不占用内存。我们可以通过unsafe.Sizeof()来获取一个数据需要占用的字节数。
fmt.Println(unsafe.Sizeof(struct{}{}))
注意:struct{}表示struct类型。struct{}{}表示struct类型的值。
打印的结果是0,说明了空结构体确实不占用内存。
那么有哪些作用呢?
- 节省资源。
- 仅仅作为占位符。
实现Set
在Go中的是没有Set这种数据结构的,因此我们可以借助map来实现Set。
如何定义好呢?
- 在使用map作为集合使用时,可以将 值类型定义为空结构体,使其作为占位符使用。
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{}{}
}
func (s Set) Delete(key string) {
delete(s, key)
}
func main() {
s := make(Set)
s.Add("Tom")
s.Add("Sam")
fmt.Println(s.Has("Tom"))
fmt.Println(s.Has("Jack"))
}
数据通道占位符
如果子协程不需要发送任何数据,可以使用空结构体代替。
只有方法的结构体
如果结构体只包含方法而没有字段,实际上可以用任何的数据结构替代。 例如可以用 type Person bool 替代 type Person struct{},但是会浪费额外的内存。
结构体的内存对齐
在Go中,会有多种数据类型,例如int8,int16,int32,int64等等。每一种类型占用的字节数不一样,我们来看下面的一个结构体:
type S1 struct{
num1 int
num2 int
}
type S2 struct {
num1 int8
num2 int32
}
func main(){
fmt.Println(unsafe.Sizeof(S1{}))
fmt.Println(unsafe.Sizeof(S2{}))
}
打印的结果分别时16和8 第一个16是因为我的电脑是在64位机器上,int默认占8个字节,所以总共是16个字节。 第二个打印8是因为内存对齐。
那么接下来我们就来聊聊Go中的结构体是如何实现内存对齐的。
CPU在访问内存的时候实际上是不会一个一个字节去读取和写入内存。相反CPU读取内存是一块一块读取的,块的大小就分为2、4、6、8、16等。上述对于结构体S2的访问,读取num1的时候,首先会以块为2大小进行访问,然后读取num2的时候,以块为4大小进行访问和写入,也就是4的整数倍来访问和写入,也就是在4开始的位置写入num2。
在Go中,我们可以通过unsafe.Alignof来表示结构体的对齐倍数(可以理解为最大块的倍数)
fmt.Println(unsafe.Alignof(S1{}))
fmt.Println(unsafe.Alignof(S2{}))
分别打印了8和4
理解了内存对齐,接下来我们再来看一个例子:
type test1 struct{
x int8
y int16
z int32
}
type test2 struct{
x int8
y int32
z int16
}
上述两个结构体的内存大小竟然不一样,分别是8和12,聪明的你应该知道是为什么了吧!
所以,在声明结构体的时候可以合理的安排字段的位置来减少占用的内存。
需要注意的是如果结构体中包含空结构体,那么它是不占用内存的,也就是不需要内存对齐,但如果是在最后一个字段,那么需要额外的内存保证安全性。