初级:1-34
- 1. 左大括号 { 不能单独放一行
- 2. 未使用的变量
- 3. 未使用的 import
- 4. 简短声明的变量只能在函数内部使用
- 5. 使用简短声明来重复声明变量
- 6. 不能使用简短声明来设置字段的值
- 7. 不小心覆盖了变量
- 8. 显式类型的变量无法使用 nil 来初始化
- 9. 直接使用值为 nil 的 slice、map
- 10. map 容量
- 11. string 类型的变量值不能为 nil
- 12. Array 类型的值作为函数参数
- 13. range 遍历 slice 和 array 时混淆了返回值
- 14. slice 和 array 其实是一维数据
- 15. 访问 map 中不存在的 key
- 16. string 类型的值是常量,不可更改
- 17. string 与 byte slice 之间的转换
- 18. string 与索引操作符
- 19. 字符串并不都是 UTF8 文本
- 20. 字符串的长度
- 21. 在多行 array、slice、map 语句中缺少 , 号
- 22. log.Fatal 和 log.Panic 不只是 log
- 23. 对内建数据结构的操作并不是同步的
- 24. range 迭代 string 得到的值
- 25. range 迭代 map
- 26. switch 中的 fallthrough 语句
- 27. 自增和自减运算
- 28. 按位取反
- 29. 运算符的优先级
- 30. 不导出的 struct 字段无法被 encode
- 31. 程序退出时还有 goroutine 在执行
- 32. 向无缓冲的 channel 发送数据,只要 receiver 准备好了就会立刻返回
- 33. 向已关闭的 channel 发送数据会造成 panic
- 34. 使用了值为 nil 的 channel
- 34. 若函数 receiver 传参是传值方式,则无法修改参数的原有值
中级:35-50
- 35. 关闭 HTTP 的响应体
- 36. 关闭 HTTP 连接
- 37. 将 JSON 中的数字解码为 interface 类型
- 38. struct、array、slice 和 map 的值比较
- 39. 从 panic 中恢复
- 40. 在 range 迭代 slice、array、map 时通过更新引用来更新元素
- 41. slice 中隐藏的数据
- 42. Slice 中数据的误用
- 43. 旧 slice
- 44. 类型声明与方法
- 45. 跳出 for-switch 和 for-select 代码块
- 46. for 语句中的迭代变量与闭包函数
- 47. defer 函数的参数值
- 48. defer 函数的执行时机
- 49. 失败的类型断言
- 50. 阻塞的 gorutinue 与资源泄露
高级:50-57
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
35. 关闭 HTTP 的响应体
使用 HTTP 标准库发起请求、获取响应时,即使你不从响应中读取任何数据或响应为空,都需要手动关闭响应体。新手很容易忘记手动关闭,或者写在了错误的位置:
// 请求失败造成 panic
func main() {
resp, err := http.Get("https://api.ipify.org?format=json")
defer resp.Body.Close() // resp 可能为 nil,不能读取 Body
if err != nil {
fmt.Println(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(string(body))
}
func checkError(err error) {
if err != nil{
log.Fatalln(err)
}
}
上边的代码能正确发起请求,但是一旦请求失败,变量 resp
值为 nil
,造成 panic:
panic: runtime error: invalid memory address or nil pointer dereference
应该先检查 HTTP 响应错误为 nil
,再调用 resp.Body.Close()
来关闭响应体:
// 大多数情况正确的示例
func main() {
resp, err := http.Get("https://api.ipify.org?format=json")
checkError(err)
defer resp.Body.Close() // 绝大多数情况下的正确关闭方式
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(string(body))
}
输出:
Get https://api.ipify.org?format=... : x509: certificate signed by unknown authority
绝大多数请求失败的情况下, resp
的值为 nil
且 err
为 non-nil
。但如果你得到的是重定向错误,那它俩的值都是 non-nil
,最后依旧可能发生内存泄露。2 个解决办法:
- 可以直接在处理 HTTP 响应错误的代码块中,直接关闭非 nil 的响应体。
- 手动调用
defer
来关闭响应体:
// 正确示例
func main() {
resp, err := http.Get("http://www.baidu.com")
// 关闭 resp.Body 的正确姿势
if resp != nil {
defer resp.Body.Close()
}
checkError(err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(string(body))
}
resp.Body.Close()
早先版本的实现是读取响应体的数据之后丢弃,保证了 keep-alive 的 HTTP 连接能重用处理不止一个请求。但 Go 的最新版本将读取并丢弃数据的任务交给了用户,如果你不处理,HTTP 连接可能会直接关闭而非重用,参考在 Go 1.5 版本文档。
如果程序大量重用 HTTP 长连接,你可能要在处理响应的逻辑代码中加入:
_, err = io.Copy(ioutil.Discard, resp.Body) // 手动丢弃读取完毕的数据
如果你需要完整读取响应,上边的代码是需要写的。比如在解码 API 的 JSON 响应数据:
json.NewDecoder(resp.Body).Decode(&data)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论