

## 正确关闭`HTTP`响应

> **注意事项**
>
> - 先检查 `err`，再 `defer close`
>
> - 必须消费完响应体，否则连接无法复用
>
> - `defer` 会延迟到函数结束，如果需要在循环中处理大量响应，建议手动关闭（避免 `defer` 累积）

```go
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` 创建一份独立的、只包含所需元素的新切片。

```go
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)
```

### 常见误区澄清

| 误区               | 正解                             |
| ------------------ | -------------------------------- |
| 指针一定在堆上     | ❌ 函数内创建和使用的指针可在栈上 |
| 接口一定在堆上     | ❌ 不逃逸的接口内部值可在栈上     |
| 结构体指针一定逃逸 | ❌ 小结构体 + 内联优化可能栈分配  |

### 验证方法

```bash
# 查看逃逸分析
go build -gcflags="-m" main.go

# 更详细信息
go build -gcflags="-m -m" main.go
```

### 优化建议

```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 处理

```go
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 只能读一次的问题时，根据你的**数据量大小**选择武器：

1. **小请求（配置、JSON）**：使用 `io.ReadAll` 存入内存，再通过 `io.NopCloser` 塞回 `r.Body` 或设置 `r.GetBody`。
2. **中等请求（签名校验）**：使用 `io.TeeReader` 边读边处理，节省一次全量扫描。
3. **大请求（文件上传）**：**禁止 ReadAll**。直接流式 `io.Copy` 到目的地，或使用临时文件做中转。



## map的坑

go的map内部是哈希表，元素会因 rehash（扩容）而移动到新的内存位置，如果允许 `&m[key]` 拿到一个指针，map 一旦扩容，这个指针就变成悬空指针（指向已失效的内存）,为了避免这种内存安全问题，Go 直接禁止对 map 值取地址,下面是示例代码：

```go
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`函数调用不需要带任何前缀。

```go
package main

import (
	. "fmt"
	. "time"
)

func main() {
	Println("Current time:", Now())
}
```

> 一般来说，句点引入不推荐使用，因为它们会导致较低的代码可读性

