- Move Windows and 算法 notes from 随手记 to 资源 directory - Migrate Golang map pitfalls and dot import notes to Golang/使用注意 - Delete omos配置 note - Add Typora custom theme reference link
6.9 KiB
正确关闭HTTP响应
注意事项
先检查
err,再defer close必须消费完响应体,否则连接无法复用
defer会延迟到函数结束,如果需要在循环中处理大量响应,建议手动关闭(避免defer累积)
resp, err := http.Get("https://example.com")
if err != nil {
// 处理请求错误(此时 resp 可能为 nil)
return err
}
// 确保在函数退出时关闭响应体
defer resp.Body.Close()
// 手动关闭,不用 defer
// io.Copy(io.Discard, resp.Body)
// resp.Body.Close()
// 读取或处理响应体(即使不需要内容也建议消费完)
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
// 使用 body ...
切片内存泄漏
即使原切片变量本身被置为
nil或超出作用域,只要还存在任何一个指向其底层数组的子切片,整个底层数组就不会被回收
如果新切片需要长期保存,而原切片很大且即将被丢弃,那么直接使用原切片[:n] 会导致新切片底层仍然引用原切片的整个底层数组,使得那个大数组无法被 GC 回收,造成内存泄漏。此时应该用 copy 创建一份独立的、只包含所需元素的新切片。
func getFirstLines() []string {
hugeLines := readHugeFile() // 假设这个切片有 100 万行,占用 100MB 内存
// 错误做法:返回子切片
return hugeLines[:3] // 虽然只返回3行,但底层数组的 100MB 内存无法被回收!
}
// 正确做法
func getFirstLines() []string {
hugeLines := readHugeFile()
// 只拷贝需要的3行
result := make([]string, 3)
copy(result, hugeLines[:3])
return result // 原来的 hugeLines 可以被GC回收,只占用很小的内存
}
堆栈
核心区别
| 特性 | 栈 | 堆 |
|---|---|---|
| 分配/释放 | 移动指针,极快 | 需要查找 + GC 回收 |
| 生命周期 | 函数返回即销毁 | GC 决定何时回收 |
| 内存布局 | 连续,无碎片 | 分散,有碎片 |
| 大小限制 | 较小(MB级) | 较大(GB级) |
逃逸判断规则
变量在以下情况逃逸到堆:
1. 返回指针 → func f() *int { x := 42; return &x }
2. 存入全局变量 → globalPtr = &x
3. 闭包捕获 → return func() { return x }
4. 发送到 channel → ch <- &x
5. 存入外部结构体 → c.Value = &x
6. 接口装箱逃逸 → return interface{}(x)
7. 反射调用 → fmt.Println(x)
8. 大对象 → make([]int, 10000)
常见误区澄清
| 误区 | 正解 |
|---|---|
| 指针一定在堆上 | ❌ 函数内创建和使用的指针可在栈上 |
| 接口一定在堆上 | ❌ 不逃逸的接口内部值可在栈上 |
| 结构体指针一定逃逸 | ❌ 小结构体 + 内联优化可能栈分配 |
验证方法
# 查看逃逸分析
go build -gcflags="-m" main.go
# 更详细信息
go build -gcflags="-m -m" main.go
优化建议
// ✅ 推荐:小对象返回值
func getUser() User {
return User{
ID: 1
}
}
// ❌ 避免:小对象返回指针(增加 GC 压力)
func getUser() *User {
return &User{
ID: 1
}
}
// ✅ 推荐:传指针给只读函数
func read(p *Data) int {
return p.Value
}
// ❌ 避免:在热路径使用 fmt.Print
// 用 strconv 或 log 代替
记忆口诀
生命周期越界 → 堆
外部有引用 → 堆
闭包捕获 → 堆
反射调用 → 堆
函数内使用 → 栈
返回值拷贝 → 栈
只读指针 → 栈
本质理解
栈 = 函数的私有工作区,函数结束自动清理
堆 = 共享仓库,需要 GC 定期打扫
逃逸分析 = 编译器判断"这个变量能不能安全地放在栈上"
类型转换/序列化
//int64转换string
strconv.FormatInt(num,10)
//序列化为string
jsoniter.ConfigCompatibleWithStandardLibrary.MarshalToString(input)
在 encode/decode JSON 数据并且数据类型是interface时,Go 默认会将数值当做 float64 处理
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Height float64 `json:"height"`
}
type UserInter struct {
Name interface{} `json:"name"`
Age interface{} `json:"age"`
Height interface{} `json:"height"`
}
func TestJson(t *testing.T) {
one := &User{
Name: "tom",
Age: 18,
}
oneJson, err := jsoniter.Marshal(one)
if err != nil {
fmt.Println(err)
return
}
var two UserInter
var three map[string]interface{}
err = jsoniter.Unmarshal(oneJson, &two)
err = jsoniter.Unmarshal(oneJson, &three)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(reflect.TypeOf(two.Name))
fmt.Println(reflect.TypeOf(two.Age)) //会打印float64
fmt.Println(reflect.TypeOf(two.Height)) //会打印float64
fmt.Println(reflect.TypeOf(three["name"]))
fmt.Println(reflect.TypeOf(three["age"])) //会打印float64
fmt.Println(reflect.TypeOf(three["height"])) //会打印float64
}
HTTP阅后即焚
处理 HTTP Body 只能读一次的问题时,根据你的数据量大小选择武器:
- 小请求(配置、JSON):使用
io.ReadAll存入内存,再通过io.NopCloser塞回r.Body或设置r.GetBody。 - 中等请求(签名校验):使用
io.TeeReader边读边处理,节省一次全量扫描。 - 大请求(文件上传):禁止 ReadAll。直接流式
io.Copy到目的地,或使用临时文件做中转。
map的坑
go的map内部是哈希表,元素会因 rehash(扩容)而移动到新的内存位置,如果允许 &m[key] 拿到一个指针,map 一旦扩容,这个指针就变成悬空指针(指向已失效的内存),为了避免这种内存安全问题,Go 直接禁止对 map 值取地址,下面是示例代码:
type User struct {
Name string
Age int
}
func main() {
m := map[int]User{1: {Name: "张三", Age: 20}}
// ❌ 编译错误:cannot assign to struct field m[1].Name in map
m[1].Name = "李四"
// ✅ 正确的替代写法
temp:=m[1]
temp.Name = "李四"
m[1]=temp
}
可寻址(addressable):能通过
&拿到它内存地址的操作数。不可寻址(not addressable):不能通过
&拿到地址的操作数。
句点引入
一个完整引入声明语句形式的引入名importname可以是一个句点(.)。 这样的引入称为句点引入。使用被句点引入的包中的导出代码要素时,限定标识符的前缀必须省略。
比如在下面这个例子中,Println和Now函数调用不需要带任何前缀。
package main
import (
. "fmt"
. "time"
)
func main() {
Println("Current time:", Now())
}
一般来说,句点引入不推荐使用,因为它们会导致较低的代码可读性