net.conn.Read、io.ReadAll和io.ReadFull三者区别
net.conn.Read
type Conn interface {
// Read reads data from the connection.
// Read can be made to time out and return an error after a fixed
// time limit; see SetDeadline and SetReadDeadline.
Read(b []byte) (n int, err error)
......
......
}注释:"从连接读取数据。可以通过 SetDeadline 或 SetReadDeadline 设置超时,超时后返回错误。"
潜台词:我是最底层的阻塞 IO,不保证读满,可能超时,你自己处理。
io.ReadAll
// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
func ReadAll(r Reader) ([]byte, error) {
b := make([]byte, 0, 512)
for {
// 利用剩余 cap 读取,第一次就是 r.Read(b[0:512]),即全部
n, err := r.Read(b[len(b):cap(b)])
b = b[:len(b)+n] // 扩展 len
if err != nil {
if err == EOF {
err = nil
}
return b, err
}
if len(b) == cap(b) {
// Add more capacity (let append pick how much).
b = append(b, 0)[:len(b)] // 扩容,指数增长
}
}
}注释:"从 r 读取直到遇到错误或 EOF,返回读到的所有数据。成功调用返回 err == nil(不是 err == EOF)。因为 ReadAll 定义为读到 EOF 才停,所以它不把 Read 返回的 EOF 当作错误报告。"
潜台词:我是动态扩容的循环读取,方便但内存不可控,适合小数据。
io.ReadFull
// ReadFull reads exactly len(buf) bytes from r into buf.
// It returns the number of bytes copied and an error if fewer bytes were read.
// The error is EOF only if no bytes were read.
// If an EOF happens after reading some but not all the bytes,
// ReadFull returns [ErrUnexpectedEOF].
// On return, n == len(buf) if and only if err == nil.
// If r returns an error having read at least len(buf) bytes, the error is dropped.
func ReadFull(r Reader, buf []byte) (n int, err error) {
return ReadAtLeast(r, buf, len(buf))
}注释:"从 r 精确读取 len(buf) 字节到 buf。返回复制的字节数,如果读得更少则返回错误。仅当 0 字节被读时错误才是 EOF。如果读了一部分后遇到 EOF,返回 ErrUnexpectedEOF。仅当 err == nil 时 n == len(buf)。如果 r 在读了至少 len(buf) 字节后返回错误,该错误被丢弃。"
潜台词:我是契约式读取,必须读满,否则报错,适合协议解析。
| 需求 | 官方意图 | 选择 |
|---|---|---|
| "读到多少算多少,我继续处理" | Read 的基础语义 | conn.Read |
| "方便地读完整个流" | ReadAll 的存在理由 | io.ReadAll |
| "必须拿到固定长度,否则失败" | ReadFull 的强契约 | io.ReadFull |
| 场景 | 推荐选择 | 原因 |
|---|---|---|
| 网络协议头(固定长度) | io.ReadFull | 必须读满,否则协议错误 |
| 小文件/配置/JSON | io.ReadAll | 便捷,但需确保大小可控 |
| 大文件/流式处理 | conn.Read + 循环 | 内存可控,避免 OOM |
| 高并发网络服务 | bufio.Reader | 减少 syscall,批量读取 |
另外
从上面 ReadAll 的源码,你会看到一个不太常见的操作:b = append(b, 0)[:len(b)]。
如注释所说,这是一个指数性扩容操作。由于 n, err := r.Read(b[len(b):cap(b)]) 和 b = b[:len(b)+n] 并不会自动触发 Slice 的懒扩容,所以要通过 b = append(b, 0)[:len(b)] 进行手动预扩容。
将 b = append(b, 0)[:len(b)] 分步来看如下:
// 假设当前
b := make([]byte, 100, 100) // len=100, cap=100
// 1. append(b, 0)
// - cap 满了,触发扩容 → 新 cap ≈ 200(2倍)
// - 追加 0,新 len = 101
tmp := append(b, 0)
// tmp: len=101, cap=200(底层新数组)
// 2. [:len(b)] 即 [:100]
// - 切片截回原来的长度,但 cap 保持 200
b = tmp[:100]
// b: len=100, cap=200(底层还是新数组)| 操作 | len | cap | 底层数组 |
|---|---|---|---|
| 初始 | 100 | 100 | 原数组 |
append(b, 0) | 101 | 200 | 新数组(2倍) |
[:100] | 100 | 200 | 新数组(保留) |
我们也可以使用 make 去进行重新分配,但实现起来麻烦一些,需要手动计算新容量,再将原数据复制过去:
// make 手动计算新容量
newCap := len(b) * 2
newBuf := make([]byte, len(b), newCap)
copy(newBuf, b)
b = newBuf
// 一行搞定,利用 runtime 优化
b = append(b, 0)[:len(b)]当然,像源码那样写也不是能直接拿来用的:
// 基础版(ReadAll 用的)
b = append(b, 0)[:len(b)] // 会在新扩容的后续第一位新增一个 0,例如原先 512,就会在 513 新增 0
// 更安全的写法(避免 0 被误读)
b = append(b, b[:0]...)[:len(b)] // 追加空切片,触发扩容
// Go 1.18+ 推荐(用 Grow)
b = slices.Grow(b, 1)[:len(b)] // 标准库支持,语义清晰官方这里能够直接用 b = append(b, 0)[:len(b)] 而不是用 b = append(b, b[:0]...)[:len(b)] 少追加这个多余的 0,是因为:
// ReadAll 核心逻辑
b := make([]byte, 0, 512)
for {
n, err := r.Read(b[len(b):cap(b)]) // 从 len 位置开始读
b = b[:len(b)+n] // 扩展到实际读到的长度
if len(b) == cap(b) {
b = append(b, 0)[:len(b)] // 扩容,但 len 不变
}
}1. 初始:b = [], len=0, cap=512
2. Read 读到 512 字节:b = [512个数据], len=512, cap=512
3. 触发扩容:append(b, 0)
- 写入 0 到 b[512](新数组的第 513 个位置)
- 但 [:512] 切回来,len 还是 512
- 这个 0 在 cap 范围内,但 len 范围外
4. 下次 Read(b[len(b):cap(b)]) 从 b[512:] 开始写,直接覆盖那个 0所以如果不是像官方这样会覆盖的情况,最好还是去掉这个多余的 0 吧。
Slice自动扩容机制
如果不记得扩容机制来复习一下吧(
// runtime/slice.go (Go 1.22+)
// nextslicecap computes the next appropriate slice length.
func nextslicecap(newLen, oldCap int) int {
newcap := oldCap
doublecap := newcap + newcap
if newLen > doublecap {
return newLen
}
const threshold = 256
if oldCap < threshold {
return doublecap
}
for {
// Transition from growing 2x for small slices
// to growing 1.25x for large slices. This formula
// gives a smooth-ish transition between the two.
newcap += (newcap + 3*threshold) >> 2
// 等同于:newcap += (newcap + 3*threshold) / 4
// We need to check `newcap >= newLen` and whether `newcap` overflowed.
// newLen is guaranteed to be larger than zero, hence
// when newcap overflows then `uint(newcap) > uint(newLen)`.
// This allows to check for both with the same comparison.
if uint(newcap) >= uint(newLen) {
break
}
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
return newLen
}
return newcap
}
RoLingG | 博客
评论(0)