G

[Golang] net.conn.Read、io.ReadAll和io.ReadFull三者区别

RoLingG Golang 2026-02-25

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)
    
    ......
    ......
}

注释:"从连接读取数据。可以通过 SetDeadlineSetReadDeadline 设置超时,超时后返回错误。"

潜台词:我是最底层的阻塞 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 == niln == len(buf)。如果 r 在读了至少 len(buf) 字节后返回错误,该错误被丢弃。"

潜台词:我是契约式读取,必须读满,否则报错,适合协议解析。

需求官方意图选择
"读到多少算多少,我继续处理"Read 的基础语义conn.Read
"方便地读完整个流"ReadAll 的存在理由io.ReadAll
"必须拿到固定长度,否则失败"ReadFull 的强契约io.ReadFull
场景推荐选择原因
网络协议头(固定长度)io.ReadFull必须读满,否则协议错误
小文件/配置/JSONio.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(底层还是新数组)
操作lencap底层数组
初始100100原数组
append(b, 0)101200新数组(2倍)
[:100]100200新数组(保留)

我们也可以使用 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
}
PREV
[Golang] 从字节Netpoll中学习相关Read问题处理

评论(0)

发布评论