Random Thoughts
Recent content on Random Thoughts
马上订阅 Random Thoughts RSS 更新: https://blog.joway.io/index.xml
重新思考 Go:Slice 只是「操作视图」
重新思考 Go 系列:这个系列希望结合工作中在 Go 编程与性能优化中遇到过的问题,探讨 Go 在语言哲学、底层实现和现实需求三者之间关系与矛盾。
Go 在语法级别上提供了 Slice 类型作为对底层内存的一个「操作视图」:
var sh []any
// ==> internal struct of []any
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
编程者可以使用一些近似 Python 的语法来表达对底层内存边界的控制:
var buf = make([]byte, 1000)
tmp := buf[:100] // {Len=100, Cap=1000}
tmp = buf[100:] // {Len=900, Cap=900}
tmp = buf[100:200:200] // {Len=100, Cap=100}
虽然 Slice 的语法看似简单,但编程者需要时刻记住一点就是 Slice 只是一个对底层内存的「操作视图」,而非底层「内存表示」,Slice 的各种语法本身并不改变底层内存。绝大部分 Slice 有关的编程陷阱根源就在于两者的差异。
Slice 陷阱:持有内存被「放大」
以最简单的从连接中读取一段数据为例,由于我们事先并不知道将会读取到多少数据,所以会预先创建 1024 字节的 buffer ,然而如果此时我们只读取到了 n bytes, n 远小于 1024,并返回了一个 len=n
的 slice,此时这个 slice 的真实内存大小依然是 1024。
func Read(conn net.Conn) []byte {
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
return buf[:n]
}
即便上一步我们内存放大的问题并不严重,比如我们的 n 恰好就是 1024。但我们依然会需要对连接读到的数据做一些简单的处理,例如我们现在需要通过 Go 的 regexp 库查询一段 email 的数据: