• 简体中文
  • Example: io.ReadFull

    io.ReadFull 用于从 io.Reader精确读取指定长度(len(buf))的数据,确保缓冲区被完全填满。

    与普通的 io.Read 不同,io.ReadFull 不会因为单次读取数据不足而提前返回成功。它会持续读取,直到满足以下条件之一:

    • 成功:读取到了指定长度(即 len(buf))的数据。
    • 失败:在缓冲区填满之前,底层 Reader 返回了错误,此时会返回已读取的字节数以及对应错误(如 io.ErrUnexpectedEOFio.EOF 或其他 I/O 错误)。

    典型应用场景

    • 二进制协议解析(如读取固定长度的 Header)
    • 网络数据包拆包(按固定长度读取消息头,避免读取到半包数据)
    • 文件头/元数据读取
    • 固定大小消息处理

    简明示例

    最简单的场景:从数据源中精确读取 5 个字节。

    package main
    import (
    	"fmt"
    	"io"
    	"strings"
    )
    
    func main() {
    	// 1. 创建 Reader,模拟数据源
    	src := strings.NewReader("Hello, io.ReadFull from gobase.net.")
    	// 2. 读取固定长度数据
    	buf := make([]byte, 5)
    	_, err := io.ReadFull(src, buf)
    	if err != nil {
    		fmt.Printf("读取失败: %v\n", err)
    		return
    	}
    	// 3. buf 包含完整读取的数据
    	fmt.Printf("%s\n", buf)
    }

    Output:

    Hello

    核心价值

    普通 reader.Read(buf) 并不保证一次读取填满整个缓冲区

    • 可能返回 n == 2,也可能返回 n == 4
    • 调用方必须自己写循环判断是否读够了所需字节。

    io.ReadFull(reader, buf) 的接口约定:只要返回 err == nil,就绝对保证 buf 已经被全部填满(即读取了 len(buf) 字节)。

    解析固定长度协议头

    在二进制协议中,通常会在消息开始位置定义固定大小 Header。

    例如:

    +-----------------------+-----------------------+
    |  Magic Code (4 bytes) |  Body Length (4 bytes)|
    +-----------------------+-----------------------+
    |                    Payload                    |
    +-----------------------+-----------------------+
    

    解析该协议时,必须先完整读取 8 字节的 Header,否则无法获知后续 Payload 的长度。

    package main
    
    import (
    	"encoding/binary"
    	"fmt"
    	"io"
    	"strings"
    )
    
    func main() {
    	// 模拟网络流数据:前 4 字节表示后续 Payload 的长度(BigEndian,数值为 5)
    	data := "\x00\x00\x00\x00\x00\x00\x00\x05hello"
    	reader := strings.NewReader(data)
    	// 协议magic头为 4 字节,长度为4字节
    	headerMagic := make([]byte, 4)
    	headerLength := make([]byte, 4)
    	
    	// 分别完整读取 Magic 和 Length 字段
    	_, err := io.ReadFull(reader, headerMagic)
    	if err != nil {
    		fmt.Printf("failed to read header: %v\n", err)
    		return
    	}
    	_, err = io.ReadFull(reader, headerLength)
    	if err != nil {
    		fmt.Printf("failed to read header: %v\n", err)
    		return
    	}
    	// 解析长度
    	length := binary.BigEndian.Uint32(headerLength)
    	fmt.Printf("Payload 预期长度: %d\n", length)
    }

    Output:

    Payload 长度: 5

    边界处理:数据不足时的错误机制

    如果底层数据源的数据量少于预期的缓冲区长度,io.ReadFull直接返回错误:

    package main
    
    import (
    	"fmt"
    	"io"
    	"strings"
    )
    
    func main() {
    	src := strings.NewReader("abc") // 仅 3 字节
    	buf := make([]byte, 5)          // 期望读 5 字节
    
    	_, err := io.ReadFull(src, buf)
    	fmt.Printf("%v\n", err)
    }
    

    返回:

    unexpected EOF

    输出中的 unexpected EOFio.ErrUnexpectedEOF,表示 Reader 在满足读取长度之前已经结束。

    这与io.EOF 不同:

    错误含义
    nil成功读满 len(buf) 字节
    io.EOF在尚未读取任何数据时,Reader 已结束
    io.ErrUnexpectedEOF已经读取了部分数据,但未填满缓冲区,数据不完整