Go 存在的坑
for range 不为人知的坑,其实以前用 php 的时候遇到过,Java 没有这个问题
// 因为 range 的时候,会创建一个临时变量 v 来接受 data 的遍历值
// 而不是你想的 append(&data[0], $data[1], ...)
data := []int{1,2,3}
result := []*int{}
for _, v := range data {
result = append(result, &v)
fmt.Println(&v) // 0xc0000ac008,0xc0000ac008,0xc0000ac008
}
for _, v := range result {
fmt.Println(*v) // [3,3,3]
}
// 正确处理方式
data := []int{1,2,3}
result := []*int{}
for i, v := range data {
result = append(result, &data[i])
}
for _, v := range result {
fmt.Println(*v)
}
结构体还是指针?
项目中函数返回值更多用的是结构体,而不是结构体指针。虽然这会提高内存拷贝开销,但另一方面可以降低 gc 压力。因为返回值如果用指针,那么变量会存储到堆,而不是栈,也就是发生了变量逃逸。
变量逃逸情况:
(1)函数中 new 或者字面量创建出来的变量,如果取变量指针作为函数返回值,那么该变量发生逃逸
(2)逃逸变量引用的指针逃逸
(3)被指针类型的 slice,map,chan 饮用的变量发生逃逸
空结构体
在不关心值的内容时,使用空结构体 struct{}{},因为不占内存空间。空结构体其实取得都是 Go 特殊变量 zerobase 的地址
Go 内存管理
内存管理三大组件:线程缓存(mcache)、中心缓存(mcentral)、页堆(mheap)。微对象和小对象会依次尝试使用三大组件进行内存分配,大对象直接在堆上分配。
Go Map
(1)Go map 以桶作为一层结构,查找元素时直接定位到桶,发生哈希冲突时,再进入桶的下一层结构 cell,遍历 cell 查找目标值。桶结构类似于 java map 的 entry 结构,cell 的作用就像 entry 构成的链表,用来解决哈希冲突。此外,当冲突太多时同样通过扩容来提高查询效率,扩容不是一次性完成的特点又与 redis 哈希的渐进式扩容方式有异曲同工之妙
(2)Map 的键必须是可比较类型,不能是切片,map,函数,且使用 float 类型作为键时需注意,值可以是任意类型
(3)Map 非线程安全,只支持并发读;map for range 遍历时的顺序是随机的!
方法接受者的判断
总结而言就是,在不需要修改接受者,且接受者体量不会很大的情况下,使用值接受者,其他情况都使用指针接受者。另外,当结构体存在锁变量时,也要使用指针接受者,避免锁拷贝
Go 倾向于无法使用值类型的情况下,才使用指针类型,因为指针类型可能会有并发问题,并且值类型也不是性能瓶颈
Context 理解
Context 只读,可不断生成新的 context 变量,形成类似树状关系的结构;context 功能在于存储只读 key-value 和进行协程间的通知
Defer 函数
Defer 在函数返回前调用,可在其内进行资源回收,注意直接调用资源回收由于提前返回或者异常导致没有执行
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。