Go 语言标准库 —— io 包(基础接口 & 拷贝函数)
🔹 核心接口
- 读取接口(最核心)
基础读取接口(所有读取操作的基石)
io.Reader interface-
定义:
type Reader interface { Read(p []byte) (n int, err error) }
-
说明:
- io 包最核心的接口
- 读取 len(p) 字节到 p 中
- 返回读取的字节数 n 和错误 err
- 读到末尾时返回 (0, io.EOF)
-
实现该接口的常见类型:
- *os.File - 文件读取
- *bytes.Buffer - 内存缓冲区
- *strings.Reader - 字符串读取
- *bytes.Reader - 字节切片读取
- net.Conn - 网络连接
-
示例(完整)
package main import ( "fmt" "io" "os" ) func main() { // os.File 实现了 io.Reader var r io.Reader = os.Stdin // 或者使用 strings.Reader // r := strings.NewReader("hello") buf := make([]byte, 10) n, err := r.Read(buf) fmt.Printf("读取了 %d 字节\n", n) fmt.Printf("错误:%v\n", err) fmt.Printf("内容:%s\n", string(buf[:n])) } -
实现 io.Reader 接口的类型详解
- os.File(文件读取)
- 说明:文件描述符,实现了 io.Reader、io.Writer、io.Seeker 等接口
- 打开文件:
os.Open(name string) (*os.File, error) - 示例:
package main import ( "fmt" "os" ) func main() { // 打开文件 file, err := os.Open("test.txt") if err != nil { fmt.Println("打开失败:", err) return } defer file.Close() // 读取文件内容 buf := make([]byte, 100) n, err := file.Read(buf) fmt.Printf("读取了 %d 字节\n", n) fmt.Printf("内容:%s\n", string(buf[:n])) }
- *bytes.Buffer(内存缓冲区)
- 说明:内存中的字节缓冲区,可读写
- 创建:
var buf bytes.Buffer或bytes.NewBufferString(s string) - 示例:
package main import ( "bytes" "fmt" ) func main() { // 从字符串创建 buf := bytes.NewBufferString("Hello World") // 读取数据 data := make([]byte, 5) n, _ := buf.Read(data) fmt.Printf("读取:%s\n", string(data)) // Hello fmt.Printf("剩余:%s\n", buf.String()) // World }
- *strings.Reader(字符串读取器)
- 说明:将字符串包装为 io.Reader
- 创建:
strings.NewReader(s string) *strings.Reader - 示例:
package main import ( "fmt" "strings" ) func main() { // 创建字符串读取器 r := strings.NewReader("Go 语言") // 读取数据 buf := make([]byte, 6) // "Go 语" 的 UTF-8 编码 n, _ := r.Read(buf) fmt.Printf("读取:%s\n", string(buf[:n])) fmt.Printf("剩余:%s\n", r.String()) }
- *bytes.Reader(字节切片读取器)
- 说明:将 []byte 包装为 io.Reader
- 创建:
bytes.NewReader(b []byte) *bytes.Reader - 示例:
package main import ( "bytes" "fmt" ) func main() { data := []byte{72, 101, 108, 108, 111} // "Hello" r := bytes.NewReader(data) buf := make([]byte, 3) n, _ := r.Read(buf) fmt.Printf("读取:%s\n", string(buf)) // Hel }
- os.File(文件读取)
-
- 写入接口(最核心)
基础写入接口(所有写入操作的基石)
io.Writer interface-
定义:
type Writer interface { Write(p []byte) (n int, err error) }
-
说明:
- io 包最核心的写入接口
- 写入 p 中的数据
- 返回写入的字节数 n 和错误 err
- 如果 n != len(p),通常会返回错误
-
实现该接口的常见类型:
- *os.File - 文件写入
- *bytes.Buffer - 内存缓冲区
- os.Stdout - 标准输出
- net.Conn - 网络连接
- *bufio.Writer - 带缓冲的写入器
-
示例(完整)
package main import ( "fmt" "io" "os" ) func main() { // os.Stdout 实现了 io.Writer var w io.Writer = os.Stdout n, err := w.Write([]byte("Hello, Writer!\n")) fmt.Printf("写入了 %d 字节\n", n) fmt.Printf("错误:%v\n", err) } -
实现 io.Writer 接口的类型详解
- os.File(文件写入)
- 说明:文件描述符,支持写入数据
- 创建文件:
os.Create(name string) (*os.File, error) - 示例:
package main import ( "fmt" "os" ) func main() { // 创建文件 file, err := os.Create("output.txt") if err != nil { fmt.Println("创建失败:", err) return } defer file.Close() // 写入数据 n, err := file.Write([]byte("Hello, File!")) if err != nil { fmt.Println("写入失败:", err) return } fmt.Printf("写入了 %d 字节\n", n) }
- *bytes.Buffer(内存缓冲区)
- 说明:内存缓冲区,写入的数据会追加到缓冲区
- 创建:
var buf bytes.Buffer - 获取内容:
buf.String()或buf.Bytes() - 示例:
package main import ( "bytes" "fmt" ) func main() { var buf bytes.Buffer // 写入数据 buf.Write([]byte("Hello")) buf.Write([]byte(" ")) buf.Write([]byte("World")) // 获取内容 fmt.Println(buf.String()) // Hello World }
- os.Stdout(标准输出)
- 说明:标准输出(通常是终端)
- 类型:*os.File
- 示例:
package main import ( "fmt" "os" ) func main() { // 直接写入标准输出 os.Stdout.Write([]byte("Hello, Stdout!\n")) // 使用变量接收 var w = os.Stdout w.Write([]byte("Hello again!\n")) }
- os.Stderr(标准错误输出)
- 说明:标准错误输出(通常是终端)
- 类型:*os.File
- 示例:
package main import ( "os" ) func main() { // 写入错误信息 os.Stderr.Write([]byte("Error occurred!\n")) }
- *bufio.Writer(带缓冲的写入器)
- 说明:提供缓冲功能,减少系统调用次数
- 创建:
bufio.NewWriter(w io.Writer) *bufio.Writer - 刷新:必须调用 Flush() 将缓冲区内容写入底层 Writer
- 示例:
package main import ( "bufio" "os" ) func main() { // 创建带缓冲的写入器 w := bufio.NewWriter(os.Stdout) // 写入数据(先存入缓冲区) w.Write([]byte("Hello")) w.Write([]byte(" ")) w.Write([]byte("Buffered")) // 必须刷新才能看到输出 w.Flush() }
- os.File(文件写入)
-
- 字节读取接口
支持逐字节读取
io.ByteReader interface-
定义:
type ByteReader interface { ReadByte() (byte, error) }
-
示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { r := strings.NewReader("hello") // 类型断言为 ByteReader if br, ok := r.(io.ByteReader); ok { b, err := br.ReadByte() if err != nil { fmt.Println("读取失败:", err) return } fmt.Printf("%c\n", b) // h } }
-
- 字节扫描接口
在 ByteReader 基础上支持回退
io.ByteScanner interface-
定义:
type ByteScanner interface { ReadByte() (byte, error) UnreadByte() error }
-
示例
r := strings.NewReader("ab") b, _ := r.ReadByte() fmt.Printf("%c\n", b) // a r.UnreadByte() b, _ = r.ReadByte() fmt.Printf("%c\n", b) // a
-
- 字节写入接口
支持逐字节写入
io.ByteWriter interface-
定义:
type ByteWriter interface { WriteByte(c byte) error }
-
说明:
- 只有一个方法:WriteByte(c byte) error
- bytes.Buffer、bufio.Writer 等都实现了这个接口
-
实现示例
package main import ( "bytes" "fmt" ) func main() { var buf bytes.Buffer // 逐字节写入 buf.WriteByte('H') buf.WriteByte('e') buf.WriteByte('l') buf.WriteByte('l') buf.WriteByte('o') fmt.Println(buf.String()) // Hello } -
bytes.Buffer 常用方法详解
- 写入单个字节
- 说明:写入一个字节到缓冲区
- 方法:
WriteByte(c byte) error - 示例:
var buf bytes.Buffer buf.WriteByte('A') buf.WriteByte('B') fmt.Println(buf.String()) // AB
- 写入字符串
- 说明:写入字符串,返回写入的字节数和错误
- 方法:
WriteString(s string) (int, error) - 示例:
var buf bytes.Buffer n, err := buf.WriteString("Hello") fmt.Println(n) // 5 fmt.Println(err) // <nil> fmt.Println(buf.String()) // Hello
- 写入字节切片
- 说明:写入字节切片,返回写入的字节数和错误
- 方法:
Write(p []byte) (int, error) - 示例:
var buf bytes.Buffer n, err := buf.Write([]byte{72, 105}) // Hi 的 ASCII fmt.Println(n) // 2 fmt.Println(buf.String()) // Hi
- 写入 Unicode 字符
- 说明:写入一个 rune(Unicode 字符),返回写入的字节数和错误
- 方法:
WriteRune(r rune) (int, error) - 示例:
var buf bytes.Buffer n, err := buf.WriteRune('你') // 中文字符 fmt.Println(n) // 3(UTF-8 编码占 3 字节) fmt.Println(buf.String()) // 你
- 读取数据
- 说明:从缓冲区读取数据到字节切片,返回读取的字节数和错误
- 方法:
Read(p []byte) (int, error) - 注意:读取后缓冲区内容会减少
- 示例:
buf := bytes.NewBufferString("Hello") data := make([]byte, 3) n, _ := buf.Read(data) fmt.Println(n) // 3 fmt.Println(string(data)) // Hel fmt.Println(buf.String()) // lo(剩余内容)
- 读取单个字节
- 说明:读取并返回下一个字节
- 方法:
ReadByte() (byte, error) - 注意:返回 byte 类型,不是 rune
- 示例:
buf := bytes.NewBufferString("ABC") b, _ := buf.ReadByte() fmt.Printf("%c\n", b) // A fmt.Println(buf.String()) // BC
- 读取到分隔符
- 说明:读取直到遇到分隔符字节,返回读取的内容
- 方法:
ReadBytes(delim byte) ([]byte, error) - 注意:返回的字节切片包含分隔符
- 示例:
buf := bytes.NewBufferString("line1\nline2\n") line, _ := buf.ReadBytes('\n') fmt.Println(string(line)) // line1\n
- 获取缓冲区内容(字节)
- 说明:返回缓冲区未读内容的字节切片
- 方法:
Bytes() []byte - 注意:返回的切片会随后续写入而变化
- 示例:
buf := bytes.NewBufferString("Hello") data := buf.Bytes() fmt.Println(string(data)) // Hello
- 获取缓冲区内容(字符串)
- 说明:返回缓冲区未读内容的字符串
- 方法:
String() string - 示例:
buf := bytes.NewBufferString("World") s := buf.String() fmt.Println(s) // World
- 获取未读长度
- 说明:返回缓冲区中未读数据的字节数
- 方法:
Len() int - 示例:
buf := bytes.NewBufferString("Hello") fmt.Println(buf.Len()) // 5 buf.ReadByte() fmt.Println(buf.Len()) // 4
- 获取容量
- 说明:返回缓冲区已分配的总容量(包括已读和未读)
- 方法:
Cap() int - 示例:
buf := bytes.NewBufferString("Hello") fmt.Println(buf.Cap()) // 初始容量
- 清空缓冲区
- 说明:重置缓冲区,丢弃所有未读数据
- 方法:
Reset() - 注意:清空后 Len() 为 0,但容量不变
- 示例:
buf := bytes.NewBufferString("Hello") fmt.Println(buf.Len()) // 5 buf.Reset() fmt.Println(buf.Len()) // 0
- 预留空间
- 说明:预留 n 字节空间,避免多次内存重新分配
- 方法:
Grow(n int) - 注意:如果 n 小于当前容量,不会缩小
- 示例:
var buf bytes.Buffer buf.Grow(100) // 预留 100 字节 fmt.Println(buf.Cap()) // >= 100
- 截断缓冲区
- 说明:截断缓冲区到 n 字节
- 方法:
Truncate(n int) - 注意:如果 n > Len(),不会扩展
- 示例:
buf := bytes.NewBufferString("Hello") buf.Truncate(3) fmt.Println(buf.String()) // Hel
- 写入到其他 Writer
- 说明:将缓冲区所有内容写入到另一个 Writer
- 方法:
WriteTo(w io.Writer) (int64, error) - 返回:写入的字节数和错误
- 示例:
buf := bytes.NewBufferString("Hello") n, _ := buf.WriteTo(os.Stdout) // 输出到控制台 fmt.Println(n) // 5
- 从 Reader 读取
- 说明:从 io.Reader 读取所有数据到缓冲区
- 方法:
ReadFrom(r io.Reader) (int64, error) - 返回:读取的字节数和错误
- 示例:
var buf bytes.Buffer n, _ := buf.ReadFrom(strings.NewReader("test")) fmt.Println(n) // 4 fmt.Println(buf.String()) // test
- 回退字节
- 说明:回退最后一次 ReadByte() 读取的字节
- 方法:
UnreadByte() error - 注意:必须在 ReadByte() 后调用,且不能连续调用
- 示例:
buf := bytes.NewBufferString("AB") b1, _ := buf.ReadByte() // 读取 A fmt.Printf("%c\n", b1) // A buf.UnreadByte() // 回退 b2, _ := buf.ReadByte() // 再次读取 A fmt.Printf("%c\n", b2) // A
- 写入单个字节
-
- 关闭接口
关闭资源
io.Closer interface-
定义:
type Closer interface { Close() error }
-
说明:
- 用于关闭资源(文件、网络连接等)
- 通常与 io.Reader 或 io.Writer 组合使用
- io.ReadCloser = io.Reader + io.Closer
- io.WriteCloser = io.Writer + io.Closer
-
示例(完整)
package main import ( "fmt" "os" ) func main() { f, err := os.Create("test.txt") if err != nil { return } defer f.Close() // 必须关闭 fmt.Println("文件已创建") }
-
- 随机读取接口
支持从指定位置读取
io.ReaderAt interface-
定义:
type ReaderAt interface { ReadAt(p []byte, off int64) (n int, err error) }
-
说明:
- 从偏移量 off 开始读取 len(p) 字节
- 不改变当前的读取位置
- 支持并发读取(多个 goroutine 可以同时读取不同位置)
- *os.File 实现了此接口
-
与 io.Reader 的区别:
- io.Read 从当前位置读取,会更新位置
- io.ReadAt 从指定位置读取,不更新位置
-
示例(完整)
package main import ( "fmt" "os" ) func main() { // 创建文件并写入数据 os.WriteFile("test.txt", []byte("0123456789"), 0644) // 打开文件 f, _ := os.Open("test.txt") defer f.Close() // 从位置 3 开始读取 4 个字节 buf := make([]byte, 4) n, _ := f.ReadAt(buf, 3) fmt.Printf("读取:%s\n", string(buf)) // 3456 fmt.Printf("字节数:%d\n", n) // 再次从位置 0 读取(不受上次影响) f.ReadAt(buf, 0) fmt.Printf("读取:%s\n", string(buf)) // 0123 }
-
- 随机写入接口
支持写入到指定位置
io.WriterAt interface-
定义:
type WriterAt interface { WriteAt(p []byte, off int64) (n int, err error) }
-
说明:
- 从偏移量 off 开始写入数据
- 不改变当前的写入位置
- 支持并发写入(需要注意数据竞争)
- *os.File 实现了此接口
-
与 io.Writer 的区别:
- io.Write 追加到当前位置,会更新位置
- io.WriteAt 写入到指定位置,不更新位置
-
示例(完整)
package main import ( "fmt" "os" ) func main() { // 创建文件 f, _ := os.Create("test.dat") defer f.Close() // 在位置 0 写入 f.WriteAt([]byte("Hello"), 0) // 在位置 6 写入 f.WriteAt([]byte("World"), 6) // 在位置 5 写入(填充中间) f.WriteAt([]byte(" "), 5) // 读取查看结果 data, _ := os.ReadFile("test.dat") fmt.Printf("文件内容:%s\n", string(data)) // Hello World }
-
- 定位接口
支持移动读写位置
io.Seeker interface-
定义:
type Seeker interface { Seek(offset int64, whence int) (int64, error) }
-
说明:
- 移动文件的读写位置
- offset:偏移量
- whence:起始位置
- io.SeekStart:从文件开头开始计算
- io.SeekCurrent:从当前位置开始计算
- io.SeekEnd:从文件末尾开始计算
- 返回新的位置
- *os.File 实现了此接口
-
示例(完整)
package main import ( "fmt" "io" "os" ) func main() { // 创建文件并写入数据 os.WriteFile("test.txt", []byte("0123456789"), 0644) f, _ := os.OpenFile("test.txt", os.O_RDWR, 0644) defer f.Close() // 从文件开头移动 3 个字节 pos, _ := f.Seek(3, io.SeekStart) fmt.Printf("位置:%d\n", pos) // 3 // 从当前位置移动 2 个字节 pos, _ = f.Seek(2, io.SeekCurrent) fmt.Printf("位置:%d\n", pos) // 5 // 从文件末尾向前移动 2 个字节 pos, _ = f.Seek(-2, io.SeekEnd) fmt.Printf("位置:%d\n", pos) // 8 // 读取当前位置的数据 buf := make([]byte, 2) f.Read(buf) fmt.Printf("读取:%s\n", string(buf)) // 89 }
-
- 读取到 Writer 接口
可将自身内容写入到 io.Writer
io.WriterTo interface-
定义:
type WriterTo interface { WriteTo(w Writer) (n int64, err error) }
-
说明:
- 将对象的内容写入到另一个 Writer
- 返回写入的字节数和错误
- io.Copy 会优先使用此接口进行优化
- *bytes.Buffer、*strings.Reader 等实现了此接口
-
示例(完整)
package main import ( "fmt" "os" "strings" ) func main() { // strings.Reader 实现了 io.WriterTo r := strings.NewReader("Hello, WriterTo!") // 直接写入到标准输出 n, _ := r.WriteTo(os.Stdout) fmt.Printf("\n写入了 %d 字节\n", n) }
-
- 从 Reader 读取接口
可从 io.Reader 读取数据到自身
io.ReaderFrom interface-
定义:
type ReaderFrom interface { ReadFrom(r Reader) (n int64, err error) }
-
说明:
- 从 Reader 读取所有数据到自身
- 返回读取的字节数和错误
- io.Copy 会优先使用此接口进行优化
- *bytes.Buffer 实现了此接口
-
示例(完整)
package main import ( "bytes" "fmt" "strings" ) func main() { // bytes.Buffer 实现了 io.ReaderFrom var buf bytes.Buffer // 从字符串读取器读取 r := strings.NewReader("Hello, ReaderFrom!") n, _ := buf.ReadFrom(r) fmt.Printf("读取了 %d 字节\n", n) fmt.Printf("内容:%s\n", buf.String()) }
-
- Rune 读取接口
支持逐 Rune(Unicode 字符)读取
io.RuneReader interface-
定义:
type RuneReader interface { ReadRune() (r rune, size int, err error) }
-
说明:
- 读取一个 Unicode 字符(rune)
- 返回 rune 值、UTF-8 编码的字节数、错误
- strings.Reader、bufio.Reader 等实现了此接口
-
示例(完整)
package main import ( "fmt" "strings" ) func main() { // strings.Reader 实现了 io.RuneReader r := strings.NewReader("你好 Go") // 类型断言 if rr, ok := r.(interface{ ReadRune() (rune, int, error) }); ok { // 读取第一个字符 r, size, _ := rr.ReadRune() fmt.Printf("字符:%c, 字节数:%d\n", r, size) // 你,3 // 读取第二个字符 r, size, _ = rr.ReadRune() fmt.Printf("字符:%c, 字节数:%d\n", r, size) // 好,3 // 读取空格 r, size, _ = rr.ReadRune() fmt.Printf("字符:%c, 字节数:%d\n", r, size) // 空格,1 } }
-
- Rune 扫描接口
在 RuneReader 基础上支持回退
io.RuneScanner interface-
定义:
type RuneScanner interface { io.RuneReader UnreadRune() error }
-
说明:
- 继承 io.RuneReader
- 支持回退最后一次读取的 rune
- bufio.Reader 实现了此接口
-
示例(完整)
package main import ( "bufio" "fmt" "strings" ) func main() { // bufio.Reader 实现了 io.RuneScanner r := bufio.NewReader(strings.NewReader("ABC")) // 读取一个字符 ch, _, _ := r.ReadRune() fmt.Printf("%c\n", ch) // A // 回退 r.UnreadRune() // 再次读取(还是 A) ch, _, _ = r.ReadRune() fmt.Printf("%c\n", ch) // A }
-
- 读写关闭组合接口
读取 + 关闭
io.ReadCloser interface-
定义:
type ReadCloser interface { Reader Closer }
-
说明:
- 组合接口:io.Reader + io.Closer
- 用于可读取且需要关闭的资源
- http.Response.Body、os.File 等实现了此接口
-
示例
var rc io.ReadCloser = os.Stdin // 使用 buf := make([]byte, 10) rc.Read(buf) rc.Close()
-
- 写关闭组合接口
写入 + 关闭
io.WriteCloser interface-
定义:
type WriteCloser interface { Writer Closer }
-
说明:
- 组合接口:io.Writer + io.Closer
- 用于可写入且需要关闭的资源
- 压缩写入器(gzip.Writer)等实现了此接口
-
示例
var wc io.WriteCloser = gzip.NewWriter(file) // 使用 wc.Write([]byte("data")) wc.Close()
-
- 读写组合接口
读取 + 写入
io.ReadWriter interface-
定义:
type ReadWriter interface { Reader Writer }
-
说明:
- 组合接口:io.Reader + io.Writer
- 用于既可读又可写的资源
- net.Conn、*bytes.Buffer 等实现了此接口
-
示例
var rw io.ReadWriter = &bytes.Buffer{} rw.Write([]byte("hello")) rw.Read(make([]byte, 5))
-
- 读写关闭组合接口
读取 + 写入 + 关闭
io.ReadWriteCloser interface-
定义:
type ReadWriteCloser interface { Reader Writer Closer }
-
说明:
- 组合接口:io.Reader + io.Writer + io.Closer
- 用于全功能的流式资源
- net.Conn 等实现了此接口
-
示例
var rwc io.ReadWriteCloser = conn rwc.Write([]byte("request")) rwc.Read(make([]byte, 100)) rwc.Close()
-
- 读写定位组合接口
读取 + 写入 + 定位
io.ReadWriteSeeker interface-
定义:
type ReadWriteSeeker interface { Reader Writer Seeker }
-
说明:
- 组合接口:io.Reader + io.Writer + io.Seeker
- 用于支持随机访问的文件
- *os.File 实现了此接口
-
示例
var rws io.ReadWriteSeeker = file rws.Write([]byte("hello")) rws.Seek(0, io.SeekStart) rws.Read(make([]byte, 5))
-
- 数据拷贝
从 src 拷贝数据到 dst(直到 EOF)
io.Copy(dst Writer, src Reader) (written int64, err error)- 说明:
- 将 src 的所有数据拷贝到 dst
- 直到 src 返回 EOF 或发生错误
- 使用 32KB 的内部缓冲区
- 如果 src 实现了 WriterTo,会调用 src.WriteTo
- 如果 dst 实现了 ReaderFrom,会调用 dst.ReadFrom
- 返回值:
- written:拷贝的总字节数
- err:遇到的错误(EOF 除外)
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { src := strings.NewReader("hello world") dst := &strings.Builder{} n, _ := io.Copy(dst, src) fmt.Println("写入字节:", n) fmt.Println("结果:", dst.String()) } - 使用场景示例
- 文件拷贝
- 示例:
src, _ := os.Open("source.txt") defer src.Close() dst, _ := os.Create("dest.txt") defer dst.Close() io.Copy(dst, src)
- 示例:
- HTTP 响应保存
- 示例:
resp, _ := http.Get("http://example.com") defer resp.Body.Close() file, _ := os.Create("page.html") defer file.Close() io.Copy(file, resp.Body)
- 示例:
- 标准输入到标准输出
- 示例:
io.Copy(os.Stdout, os.Stdin)
- 示例:
- 文件拷贝
- 说明:
- 使用缓冲区拷贝
使用自定义缓冲区拷贝
io.CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)- 说明:
- 与 io.Copy 类似,但使用提供的缓冲区
- 可以控制缓冲区大小以优化性能
- 如果 buf 为 nil,会使用 io.Copy 的默认 32KB 缓冲区
- 使用场景:
- 需要控制内存使用时
- 需要优化特定场景性能时
- 需要避免大缓冲区时
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { // 使用小缓冲区(4 字节) buf := make([]byte, 4) src := strings.NewReader("hello world") dst := &strings.Builder{} n, _ := io.CopyBuffer(dst, src, buf) fmt.Println("写入字节:", n) fmt.Println("结果:", dst.String()) } - 性能对比示例
- 小缓冲区(多次系统调用)
- 示例:
buf := make([]byte, 64) // 64 字节 io.CopyBuffer(dst, src, buf)
- 示例:
- 大缓冲区(少次系统调用)
- 示例:
buf := make([]byte, 32*1024) // 32KB io.CopyBuffer(dst, src, buf)
- 示例:
- 小缓冲区(多次系统调用)
- 说明:
- 拷贝指定字节数
拷贝指定字节数
io.CopyN(dst Writer, src Reader, n int64) (written int64, err error)- 说明:
- 从 src 拷贝恰好 n 字节到 dst
- 如果数据不足 n 字节,返回错误
- 使用内部的缓冲区进行拷贝
- 返回值:
- written:实际拷贝的字节数
- err:如果数据不足,返回 ErrUnexpectedEOF
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { src := strings.NewReader("hello world") dst := &strings.Builder{} // 只拷贝 5 字节 n, err := io.CopyN(dst, src, 5) if err != nil { fmt.Println("错误:", err) return } fmt.Println("写入字节:", n) fmt.Println("结果:", dst.String()) } - 错误情况示例
- 数据不足
- 示例:
src := strings.NewReader("hi") dst := &strings.Builder{} n, err := io.CopyN(dst, src, 5) fmt.Println(n) // 2 fmt.Println(err) // unexpected EOF
- 示例:
- 从文件读取前 N 字节
- 示例:
file, _ := os.Open("data.bin") defer file.Close() dst := &bytes.Buffer{} io.CopyN(dst, file, 1024) // 只读取前 1KB
- 示例:
- 数据不足
- 说明:
- 丢弃写入
一个“黑洞“写入器(数据会被丢弃)
io.Discard (Writer)- 说明:
- 实现了 io.Writer 接口
- 所有写入的数据都会被丢弃
- Write 方法总是返回成功
- 类似 Unix 的 /dev/null
- 用途:
- 忽略不需要的输出
- 测试性能(测量最大吞吐量)
- 作为占位符 Writer
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { src := strings.NewReader("hello") // 丢弃所有数据 n, _ := io.Copy(io.Discard, src) fmt.Println("丢弃了", n, "字节") } - 使用场景示例
- 忽略 HTTP 响应体
- 示例:
resp, _ := http.Get("http://example.com") defer resp.Body.Close() io.Copy(io.Discard, resp.Body) // 忽略响应体
- 示例:
- 性能测试
- 示例:
// 测试最大读取速度 data := make([]byte, 1024*1024) src := bytes.NewReader(data) io.Copy(io.Discard, src)
- 示例:
- 跳过错误输出
- 示例:
cmd := exec.Command("noisy-command") cmd.Stdout = io.Discard // 忽略标准输出 cmd.Stderr = io.Discard // 忽略错误输出 cmd.Run()
- 示例:
- 忽略 HTTP 响应体
- 说明:
🔥 总结
- ByteReader 👉 逐字节读取
- ByteScanner 👉 可回退读取
- ByteWriter 👉 逐字节写入
- Closer 👉 关闭资源
👉 拷贝函数:
- Copy 👉 全量拷贝
- CopyBuffer 👉 自定义缓冲
- CopyN 👉 指定长度拷贝
- Discard 👉 丢弃输出
Go语言标准库 —— io 包(错误变量)
🔹 错误变量(error)
👉 以下均为 io 包中定义的标准错误
👉 推荐写法:
if err == io.EOF { ... }
- 文件结束错误
表示读取到文件末尾(最常用)
var EOF = errors.New("EOF")- 说明:
- 表示读取操作已到达文件末尾
- 这是正常情况,不是真正的错误
- 读取循环应该检查这个错误并正常退出
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { r := strings.NewReader("hello") buf := make([]byte, 10) for { n, err := r.Read(buf) if err == io.EOF { fmt.Println("读取完成") break } if err != nil { fmt.Println("错误:", err) return } fmt.Printf("读取:%s\n", string(buf[:n])) } }
- 说明:
- 意外结束错误
表示在读取完整数据前意外到达文件末尾
var ErrUnexpectedEOF = errors.New("unexpected EOF")- 说明:
- 与 EOF 不同,这是一个真正的错误
- 表示期望读取更多数据,但数据源提前结束
- 通常表示数据损坏或格式错误
- 示例(完整)
package main import ( "bytes" "encoding/binary" "fmt" "io" ) func main() { // 期望读取一个 int32(4 字节),但只有 2 字节 data := []byte{1, 2} r := bytes.NewReader(data) var value int32 err := binary.Read(r, binary.LittleEndian, &value) if err == io.ErrUnexpectedEOF { fmt.Println("数据不完整:", err) } }
- 说明:
- 短写入错误
表示只写入了部分数据
var ErrShortWrite = errors.New("short write")- 说明:
- 表示写入操作没有写入所有数据
- 通常发生在写入固定大小的存储时
- 示例(完整)
package main import ( "fmt" "io" ) // 自定义 Writer,只接受部分数据 type LimitedWriter struct { Remaining int } func (w *LimitedWriter) Write(p []byte) (int, error) { if len(p) > w.Remaining { w.Remaining = 0 return w.Remaining, io.ErrShortWrite } w.Remaining -= len(p) return len(p), nil } func main() { lw := &LimitedWriter{Remaining: 5} n, err := lw.Write([]byte("hello world")) if err == io.ErrShortWrite { fmt.Printf("只写入了 %d 字节,错误:%v\n", n, err) } }
- 说明:
- 短缓冲区错误
表示提供的缓冲区太小
var ErrShortBuffer = errors.New("short buffer")- 说明:
- 表示提供的缓冲区不足以容纳数据
- 通常发生在读取操作需要最小缓冲区时
- 示例(完整)
package main import ( "bytes" "fmt" "io" ) func main() { // ReadAtLeast 需要至少读取 10 字节 r := &bytes.Reader{} buf := make([]byte, 5) // 但缓冲区只有 5 字节 _, err := io.ReadAtLeast(r, buf, 10) if err == io.ErrShortBuffer { fmt.Println("缓冲区太小:", err) } }
- 说明:
- 管道关闭错误
表示在已关闭的管道上执行操作
var ErrClosedPipe = errors.New("io: read/write on closed pipe")- 说明:
- 发生在 io.Pipe 的读取端或写入端已关闭后
- 尝试在关闭的管道上读写会返回此错误
- 示例(完整)
package main import ( "fmt" "io" ) func main() { r, w := io.Pipe() w.Close() // 先关闭写入端 buf := make([]byte, 10) _, err := r.Read(buf) if err == io.ErrClosedPipe { fmt.Println("管道已关闭:", err) } }
- 说明:
- 无进展错误
表示读取器长时间没有进展
var ErrNoProgress = errors.New("io: read/write on closed pipe")- 说明:
- 用于检测读取器长时间没有返回数据也没有返回错误的情况
- 通常用于包装读取器进行超时检测
- 示例
// 这个错误较少直接使用 // 通常由 io.NoProgressTimeout 等机制触发 if err == io.ErrNoProgress { fmt.Println("读取无进展") }
- 说明:
🔹 Error() 方法说明
👉 所有 error 都实现:
err.Error()
👉 一般无需手动调用(fmt 会自动调用)
🔥 总结
- EOF 👉 正常结束(最重要)
- ErrUnexpectedEOF 👉 意外结束(真正错误)
- ErrShortWrite 👉 写入不完整
- ErrShortBuffer 👉 缓冲区太小
- ErrClosedPipe 👉 管道已关闭
- ErrNoProgress 👉 读取无进展
👉 判断错误统一写法:
if err == io.EOF {
// 正常结束
}
if err == io.ErrUnexpectedEOF {
// 数据损坏
}
if err == io.ErrShortWrite {
// 写入不完整
}
Go语言标准库 —— io 包(组合 Reader / Writer)
- 限制读取
io.LimitReader(r Reader, n int64) Reader
返回一个最多读取 n 字节的 Reader。- 说明:
- 超过 n 后返回 EOF
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { r := strings.NewReader("hello world") lr := io.LimitReader(r, 5) buf := make([]byte, 10) n, _ := lr.Read(buf) fmt.Println(string(buf[:n])) // hello }
- 说明:
- 限制读取结构体
限制读取的底层实现
io.LimitedReader struct- 字段:
- R Reader
- N int64 // 剩余可读字节数
- 说明:
- 包装一个 io.Reader,限制最多读取 N 字节
- 当 N <= 0 时,Read 会返回 EOF
- 每次读取后会自动减少 N 的值
- 常用方法详解
- Read 方法
- 说明:读取数据,最多读取 N 字节
- 方法:
Read(p []byte) (n int, err error) - 注意:读取后 N 会减少相应的字节数
- 示例:
lr := &io.LimitedReader{ R: strings.NewReader("hello world"), N: 5, } buf := make([]byte, 10) n, _ := lr.Read(buf) fmt.Println(n) // 5 fmt.Println(string(buf[:n])) // hello fmt.Println(lr.N) // 0(剩余 0 字节)
- 再次读取(N=0 时)
- 说明:当 N=0 时,Read 会立即返回 EOF
- 示例:
lr := &io.LimitedReader{ R: strings.NewReader("hello"), N: 0, } buf := make([]byte, 5) n, err := lr.Read(buf) fmt.Println(n) // 0 fmt.Println(err) // io.EOF
- Read 方法
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { lr := &io.LimitedReader{ R: strings.NewReader("hello world"), N: 5, } // 第一次读取 buf := make([]byte, 10) n, err := lr.Read(buf) fmt.Printf("读取:%s, 剩余:%d, 错误:%v\n", string(buf[:n]), lr.N, err) // 第二次读取(N=0,返回 EOF) n, err = lr.Read(buf) fmt.Printf("读取:%d, 错误:%v\n", n, err) }
- 字段:
- 多 Reader 合并
将多个 Reader 串联
io.MultiReader(readers ...Reader) Reader- 说明:
- 将多个 io.Reader 串联成一个 io.Reader
- 按顺序读取每个 Reader,直到所有 Reader 都返回 EOF
- 返回的 Reader 实现了 io.Reader 接口
- 工作原理:
- 先读取第一个 Reader,直到 EOF
- 然后自动切换到下一个 Reader
- 所有 Reader 都读完才返回 EOF
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { r1 := strings.NewReader("hello ") r2 := strings.NewReader("world") r3 := strings.NewReader("!") // 串联多个 Reader r := io.MultiReader(r1, r2, r3) buf := make([]byte, 20) n, _ := r.Read(buf) fmt.Println(string(buf[:n])) // hello world! } - 使用场景示例
- 合并多个文件
- 示例:
f1, _ := os.Open("part1.txt") f2, _ := os.Open("part2.txt") f3, _ := os.Open("part3.txt") r := io.MultiReader(f1, f2, f3) io.Copy(os.Stdout, r)
- 示例:
- 添加文件头尾
- 示例:
header := strings.NewReader("{ \"data\": [") footer := strings.NewReader("] }") r := io.MultiReader(header, fileReader, footer) data, _ := io.ReadAll(r)
- 示例:
- 动态添加 Reader
- 示例:
readers := []io.Reader{ strings.NewReader("start"), file, strings.NewReader("end"), } r := io.MultiReader(readers...)
- 示例:
- 合并多个文件
- 说明:
- 多 Writer 写入
将数据同时写入多个 Writer
io.MultiWriter(writers ...Writer) Writer- 说明:
- 将多个 io.Writer 合并成一个 io.Writer
- 每次 Write 会写入到所有 Writer
- 返回的 Writer 实现了 io.Writer 接口
- 工作原理:
- 调用 Write 时,按顺序写入每个 Writer
- 如果某个 Writer 返回错误,立即停止并返回该错误
- 所有 Writer 都成功才返回成功
- 示例(完整)
package main import ( "bytes" "fmt" "io" "os" ) func main() { var buf bytes.Buffer // 同时写入 stdout 和 buffer w := io.MultiWriter(os.Stdout, &buf) w.Write([]byte("hello\n")) fmt.Println("缓冲区内容:", buf.String()) } - 使用场景示例
- 日志输出到文件和控制台
- 示例:
logFile, _ := os.Create("app.log") defer logFile.Close() w := io.MultiWriter(os.Stdout, logFile) logger := log.New(w, "", 0) logger.Println("启动服务")
- 示例:
- 数据备份
- 示例:
var backup bytes.Buffer w := io.MultiWriter(destination, &backup) io.Copy(w, source) // 此时 destination 和 backup 都有相同数据
- 示例:
- 写入多个文件
- 示例:
f1, _ := os.Create("copy1.txt") f2, _ := os.Create("copy2.txt") defer f1.Close() defer f2.Close() w := io.MultiWriter(f1, f2) w.Write([]byte("duplicate"))
- 示例:
- 日志输出到文件和控制台
- 说明:
- 偏移写入器
创建带偏移的 Writer
io.NewOffsetWriter(w WriterAt, off int64) *io.OffsetWrite- 说明:
- 创建一个从指定偏移量开始写入的 Writer
- 内部维护一个偏移量计数器
- 每次 Write 后自动更新偏移量
- 返回的 OffsetWriter 实现了 io.Writer 和 io.WriterAt 接口
- 使用场景:
- 在文件的特定位置开始写入
- 跳过文件头部(如保留头部空间)
- 连续写入到不同位置
- 示例(完整)
package main import ( "fmt" "io" "os" ) func main() { f, _ := os.Create("test.txt") defer f.Close() // 从位置 5 开始写入 w := io.NewOffsetWriter(f, 5) w.Write([]byte("hello")) // 写入到位置 5-9 w.Write([]byte(" ")) // 写入到位置 10 w.Write([]byte("world")) // 写入到位置 11-15 fmt.Println("写入完成") // 读取查看结果 data, _ := os.ReadFile("test.txt") fmt.Printf("文件内容:%q\n", string(data)) } - 使用场景示例
- 跳过文件头部
- 示例:
file, _ := os.Create("data.bin") // 跳过前 128 字节(保留给头部) w := io.NewOffsetWriter(file, 128) w.Write(data) // 然后再写入头部 file.WriteAt(header, 0)
- 示例:
- 连续写入
- 示例:
file, _ := os.OpenFile("log.bin", os.O_RDWR|os.O_CREATE, 0644) w := io.NewOffsetWriter(file, 0) // 每次写入自动更新偏移量 w.Write(record1) w.Write(record2) w.Write(record3)
- 示例:
- 跳过文件头部
- 说明:
- 分段读取器
读取指定区间的数据
io.NewSectionReader(r ReaderAt, off int64, n int64) *io.SectionReader- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { r := strings.NewReader("hello world") sr := io.NewSectionReader(r, 6, 5) buf := make([]byte, 5) sr.Read(buf) fmt.Println(string(buf)) // world }
- 示例(完整)
- 空关闭包装
将 Reader 包装为 ReadCloser(无实际关闭)
io.NopCloser(r Reader) ReadCloser- 说明:
- 将一个 io.Reader 包装成 io.ReadCloser
- Close 方法什么都不做(no-op)
- 用于需要 ReadCloser 但实际不需要关闭的场景
- 使用场景:
- 函数签名需要 io.ReadCloser,但数据源不需要关闭
- 测试代码中模拟 io.ReadCloser
- 包装内存数据源
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { r := strings.NewReader("hello") // 包装为 ReadCloser rc := io.NopCloser(r) // 正常读取 buf := make([]byte, 5) rc.Read(buf) fmt.Println(string(buf)) // Close 什么都不做 rc.Close() } - 使用场景示例
- 满足接口要求
- 示例:
func Process(r io.ReadCloser) { defer r.Close() // 处理... } // 使用 NopCloser 传递字符串 Process(io.NopCloser(strings.NewReader("data")))
- 示例:
- HTTP 测试
- 示例:
// 模拟 HTTP 响应体 resp := &http.Response{ Body: io.NopCloser(strings.NewReader("test")), }
- 示例:
- 包装 bytes.Buffer
- 示例:
var buf bytes.Buffer buf.WriteString("data") rc := io.NopCloser(&buf) // 现在 rc 实现了 io.ReadCloser
- 示例:
- 满足接口要求
- 说明:
- 偏移写入器结构体
带偏移的写入器
io.OffsetWriter struct- 说明:
- 实现了 io.Writer 和 io.WriterAt 接口
- 内部维护当前的偏移量
- 通常通过 io.NewOffsetWriter 创建
- 常用方法详解
- Write 方法
- 说明:在当前偏移量位置写入数据
- 方法:
Write(p []byte) (n int, err error) - 注意:写入后会自动更新偏移量
- 示例:
f, _ := os.Create("file.txt") w := io.NewOffsetWriter(f, 0) w.Write([]byte("hello")) // 偏移量变为 5 w.Write([]byte(" ")) // 偏移量变为 6 w.Write([]byte("world")) // 偏移量变为 11
- WriteAt 方法
- 说明:在指定偏移量位置写入数据
- 方法:
WriteAt(p []byte, off int64) (n int, err error) - 注意:会更新内部偏移量为 off + int64(len(p))
- 示例:
f, _ := os.Create("file.txt") w := io.NewOffsetWriter(f, 0) w.WriteAt([]byte("hello"), 10) // 在位置 10 写入 // 内部偏移量现在是 15
- Write 方法
- 示例(完整)
package main import ( "fmt" "io" "os" ) func main() { f, _ := os.Create("file.txt") defer f.Close() // 从位置 2 开始 w := io.NewOffsetWriter(f, 2) // 普通写入(从位置 2 开始) w.Write([]byte("ABC")) // 指定位置写入 w.WriteAt([]byte("X"), 0) // 在位置 0 写入 fmt.Println("写入完成") // 读取查看结果 data, _ := os.ReadFile("file.txt") fmt.Printf("文件内容:%q\n", string(data)) }
- 说明:
🔥 总结
- LimitReader 👉 限制读取长度
- LimitedReader 👉 限制读取结构体
- MultiReader 👉 多输入流合并
- MultiWriter 👉 多输出流写入
- NewOffsetWriter 👉 偏移写入
- NewSectionReader 👉 区间读取
- NopCloser 👉 包装关闭接口
- OffsetWriter 👉 偏移写入结构体
Go语言标准库 —— io 包(Pipe & Reader接口族)
- 管道(内存同步)
创建一个同步内存管道(读写阻塞)
io.Pipe() (*io.PipeReader, *io.PipeWriter)- 说明:
- 写入端写数据 → 读端才能读取
- 常用于 goroutine 通信
- 示例(完整)
package main import ( "fmt" "io" ) func main() { r, w := io.Pipe() go func() { w.Write([]byte("hello pipe")) w.Close() }() buf := make([]byte, 20) n, _ := r.Read(buf) fmt.Println(string(buf[:n])) }
- 说明:
- 管道读取端
管道读取端结构体
io.PipeReader struct- 说明:
- 实现了 io.Reader 接口
- 与 PipeWriter 配对使用
- 读取操作会阻塞,直到有数据写入
- 常用方法详解
- Read 方法
- 说明:从管道读取数据
- 方法:
Read(p []byte) (n int, err error) - 注意:如果写入端未写入数据,会阻塞等待
- 示例:
r, w := io.Pipe() go func() { w.Write([]byte("hello")) w.Close() }() buf := make([]byte, 10) n, err := r.Read(buf) fmt.Println(string(buf[:n])) // hello
- Close 方法
- 说明:关闭读取端
- 方法:
Close() error - 注意:关闭后写入端会收到 ErrClosedPipe 错误
- 示例:
r, w := io.Pipe() r.Close() // 关闭读取端 _, err := w.Write([]byte("data")) fmt.Println(err) // io: read/write on closed pipe
- CloseWithError 方法
- 说明:关闭读取端并返回指定错误
- 方法:
CloseWithError(err error) error - 注意:写入端会收到这个错误
- 示例:
r, w := io.Pipe() go func() { r.CloseWithError(fmt.Errorf("自定义错误")) }() _, err := w.Write([]byte("data")) fmt.Println(err) // 自定义错误
- Read 方法
- 示例(完整)
package main import ( "fmt" "io" ) func main() { r, w := io.Pipe() // 写入端在另一个 goroutine 中 go func() { w.Write([]byte("hello")) w.Write([]byte(" ")) w.Write([]byte("pipe")) w.Close() }() // 读取端 buf := make([]byte, 20) total := 0 for { n, err := r.Read(buf) if err == io.EOF { break } if err != nil { fmt.Println("错误:", err) return } total += n } fmt.Println("读取完成,总字节:", total) }
- 说明:
- 管道写入端
管道写入端结构体
io.PipeWriter struct- 说明:
- 实现了 io.Writer 接口
- 与 PipeReader 配对使用
- 写入操作会阻塞,直到读取端读取数据
- 常用方法详解
- Write 方法
- 说明:向管道写入数据
- 方法:
Write(p []byte) (n int, err error) - 注意:如果读取端未读取,会阻塞等待
- 示例:
r, w := io.Pipe() go func() { w.Write([]byte("hello")) w.Close() }() data, _ := io.ReadAll(r) fmt.Println(string(data)) // hello
- Close 方法
- 说明:关闭写入端
- 方法:
Close() error - 注意:关闭后读取端会收到 EOF
- 示例:
r, w := io.Pipe() go func() { w.Write([]byte("data")) w.Close() // 关闭写入端 }() data, _ := io.ReadAll(r) fmt.Println(string(data)) // data
- CloseWithError 方法
- 说明:关闭写入端并返回指定错误给读取端
- 方法:
CloseWithError(err error) error - 注意:读取端会收到这个错误而不是 EOF
- 示例:
r, w := io.Pipe() go func() { w.CloseWithError(fmt.Errorf("写入失败")) }() _, err := io.ReadAll(r) fmt.Println(err) // 写入失败
- Write 方法
- 示例(完整)
package main import ( "fmt" "io" ) func main() { r, w := io.Pipe() // 写入端 go func() { // 写入多批数据 w.Write([]byte("line1\n")) w.Write([]byte("line2\n")) w.Write([]byte("line3\n")) // 正常关闭 w.Close() }() // 读取端 data, err := io.ReadAll(r) if err != nil { fmt.Println("错误:", err) return } fmt.Printf("读取内容:\n%s", string(data)) }
- 说明:
- 读取全部
读取所有数据直到 EOF
io.ReadAll(r Reader) ([]byte, error)- 说明:
- 从 Reader 读取所有剩余数据
- 返回读取的字节切片和错误
- 内部会自动扩展缓冲区,无需手动分配
- 注意事项:
- 如果数据源很大,会占用大量内存
- 对于大文件,建议使用 io.Copy 代替
- 读取完成后会返回 EOF 错误(通常忽略)
- 示例(完整)
package main import ( "fmt" "io" "os" ) func main() { // 从文件读取所有内容 data, err := io.ReadAll(os.Stdin) if err != nil { fmt.Println("错误:", err) return } fmt.Printf("读取了 %d 字节\n", len(data)) fmt.Printf("内容:%s\n", string(data)) } - 从不同源读取示例
- 从字符串读取
- 示例:
data, _ := io.ReadAll(strings.NewReader("hello")) fmt.Println(string(data)) // hello
- 示例:
- 从 HTTP 响应读取
- 示例:
resp, _ := http.Get("http://example.com") defer resp.Body.Close() data, _ := io.ReadAll(resp.Body) fmt.Println(string(data))
- 示例:
- 从管道读取
- 示例:
r, w := io.Pipe() go func() { w.Write([]byte("hello")) w.Close() }() data, _ := io.ReadAll(r) fmt.Println(string(data)) // hello
- 示例:
- 从字符串读取
- 说明:
- 至少读取 N 字节
至少读取指定数量的字节
io.ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)- 说明:
- 尝试读取至少 min 字节到 buf 中
- 返回实际读取的字节数和错误
- 如果 buf 长度小于 min,返回 ErrShortBuffer
- 返回值:
- n >= min:成功读取至少 min 字节
- n < min 且 err == nil:读到 EOF
- err == ErrShortBuffer:buf 太小
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { r := strings.NewReader("hello world") buf := make([]byte, 10) // 至少读取 5 字节 n, err := io.ReadAtLeast(r, buf, 5) if err != nil { fmt.Println("错误:", err) return } fmt.Printf("读取了 %d 字节\n", n) fmt.Printf("内容:%s\n", string(buf[:n])) } - 错误情况示例
- 缓冲区太小
- 示例:
r := strings.NewReader("hello") buf := make([]byte, 3) _, err := io.ReadAtLeast(r, buf, 5) fmt.Println(err) // io: short buffer
- 示例:
- 数据不足
- 示例:
r := strings.NewReader("hi") buf := make([]byte, 5) n, err := io.ReadAtLeast(r, buf, 5) fmt.Println(n) // 2 fmt.Println(err) // io: unexpected EOF
- 示例:
- 缓冲区太小
- 说明:
- ReadCloser 接口
io.ReadCloser interface
-
定义:
type ReadCloser interface { Reader Closer }
-
示例
rc := io.NopCloser(strings.NewReader("hello")) data, _ := io.ReadAll(rc) fmt.Println(string(data)) rc.Close()
-
- 读取固定长度
必须读取完整缓冲区长度
io.ReadFull(r Reader, buf []byte) (n int, err error)- 说明:
- 必须读取恰好 len(buf) 字节
- 如果数据不足,返回 ErrUnexpectedEOF
- 常用于读取固定长度的数据(如二进制协议)
- 返回值:
- n == len(buf) 且 err == nil:成功
- err == ErrUnexpectedEOF:数据不足
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { r := strings.NewReader("hello world") buf := make([]byte, 5) // 必须读取 5 字节 n, err := io.ReadFull(r, buf) if err != nil { fmt.Println("错误:", err) return } fmt.Printf("读取了 %d 字节:%s\n", n, string(buf)) } - 错误情况示例
- 数据不足
- 示例:
r := strings.NewReader("hi") buf := make([]byte, 5) n, err := io.ReadFull(r, buf) fmt.Println(n) // 2 fmt.Println(err) // unexpected EOF
- 示例:
- 读取文件头部
- 示例:
file, _ := os.Open("image.png") defer file.Close() // 读取 PNG 文件头(8 字节) header := make([]byte, 8) _, err := io.ReadFull(file, header) if err != nil { fmt.Println("不是有效的 PNG 文件") }
- 示例:
- 数据不足
- 说明:
- 组合接口(Read + Seek + Close)
io.ReadSeekCloser interface
-
定义:
type ReadSeekCloser interface { Reader Seeker Closer }
-
- 读取 + 定位
io.ReadSeeker interface
-
定义:
type ReadSeeker interface { Reader Seeker }
-
- 读写关闭
io.ReadWriteCloser interface
-
定义:
type ReadWriteCloser interface { Reader Writer Closer }
-
- 读写定位
io.ReadWriteSeeker interface
-
定义:
type ReadWriteSeeker interface { Reader Writer Seeker }
-
- 读写接口
io.ReadWriter interface
-
定义:
type ReadWriter interface { Reader Writer }
-
- 读取接口(核心)
io.Reader interface
-
定义:
type Reader interface { Read(p []byte) (n int, err error) }
-
示例
var r io.Reader = strings.NewReader("hello") buf := make([]byte, 5) r.Read(buf) fmt.Println(string(buf))
-
- 随机读取
io.ReaderAt interface
-
定义:
type ReaderAt interface { ReadAt(p []byte, off int64) (n int, err error) }
-
示例
r := strings.NewReader("hello world") buf := make([]byte, 5) r.ReadAt(buf, 6) fmt.Println(string(buf)) // world
-
- ReaderFrom 接口
io.ReaderFrom interface
-
定义:
type ReaderFrom interface { ReadFrom(r Reader) (n int64, err error) }
-
说明:
- 常用于优化 io.Copy
-
- rune 读取
io.RuneReader interface
-
定义:
type RuneReader interface { ReadRune() (r rune, size int, err error) }
-
示例
r := strings.NewReader("你好") ch, _, _ := r.ReadRune() fmt.Println(string(ch))
-
- rune 扫描
io.RuneScanner interface
-
定义:
type RuneScanner interface { RuneReader UnreadRune() error }
-
示例
r := strings.NewReader("ab") ch, _, _ := r.ReadRune() fmt.Println(string(ch)) // a r.UnreadRune() ch, _, _ = r.ReadRune() fmt.Println(string(ch)) // a
-
🔥 总结
- Pipe 👉 内存管道通信
- PipeReader / PipeWriter 👉 管道两端
👉 读取函数:
- ReadAll 👉 全量读取
- ReadFull 👉 固定读取
- ReadAtLeast 👉 最少读取
👉 核心接口:
- Reader 👉 最核心接口
- Writer 👉 写入接口
- Closer 👉 关闭接口
👉 组合接口:
- ReadCloser / ReadWriter / ReadSeeker 等
👉 特殊接口:
- ReaderAt 👉 随机读取
- RuneReader 👉 Unicode读取
- RuneScanner 👉 可回退字符
Go语言标准库 —— io 包(Seek & 高级Reader)
- 分段读取器
用于从 ReaderAt 中读取指定区间数据
io.SectionReader struct- 字段:
- 内部封装了 io.ReaderAt
- 固定了起始偏移量和长度
- 说明:
- 允许从大的数据源中读取特定区间
- 支持 Seek 操作,但范围限制在区间内
- 实现了 io.Reader、io.ReaderAt、io.Seeker 接口
- 常用方法详解
- Read 方法
- 说明:从当前区间位置读取数据
- 方法:
Read(p []byte) (n int, err error) - 注意:只能读取区间内的数据
- 示例:
r := strings.NewReader("hello world") sr := io.NewSectionReader(r, 6, 5) // 从位置 6 开始,长度 5 buf := make([]byte, 5) n, _ := sr.Read(buf) fmt.Println(string(buf)) // world
- ReadAt 方法
- 说明:从相对于区间起始的位置读取数据
- 方法:
ReadAt(p []byte, off int64) (n int, err error) - 注意:off 是相对于区间起始位置(0)的偏移
- 示例:
r := strings.NewReader("hello world") sr := io.NewSectionReader(r, 6, 5) // 区间:"world" buf := make([]byte, 3) sr.ReadAt(buf, 2) // 从区间内位置 2 读取 fmt.Println(string(buf)) // rld
- Seek 方法
- 说明:移动区间内的读取位置
- 方法:
Seek(offset int64, whence int) (int64, error) - 注意:位置不能超出区间范围 [0, size)
- 示例:
r := strings.NewReader("hello world") sr := io.NewSectionReader(r, 6, 5) // 区间:"world" pos, _ := sr.Seek(2, io.SeekStart) fmt.Println("位置:", pos) // 2 buf := make([]byte, 3) sr.Read(buf) fmt.Println(string(buf)) // rld
- Size 方法
- 说明:返回区间的总长度
- 方法:
Size() int64 - 注意:返回创建时指定的长度 n
- 示例:
r := strings.NewReader("hello world") sr := io.NewSectionReader(r, 6, 5) fmt.Println("区间大小:", sr.Size()) // 5
- Read 方法
- 示例(完整)
package main import ( "fmt" "io" "strings" ) func main() { // 创建一个大字符串 r := strings.NewReader("hello world this is a test") // 创建区间读取器:从位置 6 开始,长度 5("world") sr := io.NewSectionReader(r, 6, 5) // 方法 1:直接读取 buf1 := make([]byte, 5) sr.Read(buf1) fmt.Println("读取:", string(buf1)) // world // 方法 2:使用 Seek 定位 sr.Seek(0, io.SeekStart) // 回到开头 buf2 := make([]byte, 3) sr.Read(buf2) fmt.Println("部分读取:", string(buf2)) // wor // 方法 3:使用 ReadAt buf3 := make([]byte, 2) sr.ReadAt(buf3, 3) // 从区间内位置 3 读取 fmt.Println("ReadAt:", string(buf3)) // ld // 查看区间大小 fmt.Println("区间大小:", sr.Size()) // 5 }
- 字段:
- Seek 常量(当前位置)
io.SeekCurrent (int)
相对于当前位置移动。- 示例
r := strings.NewReader("hello") r.Seek(2, io.SeekStart) r.Seek(1, io.SeekCurrent) buf := make([]byte, 2) r.Read(buf) fmt.Println(string(buf)) // lo
- 示例
- Seek 常量(末尾)
io.SeekEnd (int)
相对于文件末尾移动。- 示例
r := strings.NewReader("hello") r.Seek(-2, io.SeekEnd) buf := make([]byte, 2) r.Read(buf) fmt.Println(string(buf)) // lo
- 示例
- Seek 常量(开头)
io.SeekStart (int)
相对于起始位置移动。- 示例
r := strings.NewReader("hello") r.Seek(1, io.SeekStart) buf := make([]byte, 2) r.Read(buf) fmt.Println(string(buf)) // el
- 示例
- 定位接口
io.Seeker interface
支持位置移动的接口。-
定义:
type Seeker interface { Seek(offset int64, whence int) (int64, error) }
-
示例
var s io.Seeker = strings.NewReader("hello") s.Seek(2, io.SeekStart) buf := make([]byte, 3) s.(io.Reader).Read(buf) fmt.Println(string(buf)) // llo
-
- 字符串写入接口
io.StringWriter interface
支持写入字符串(优化性能)。-
定义:
type StringWriter interface { WriteString(s string) (n int, err error) }
-
示例
package main import ( "bytes" "fmt" ) func main() { var buf bytes.Buffer buf.WriteString("hello") buf.WriteString(" world") fmt.Println(buf.String()) }
-
- TeeReader(分流读取)
从 r 读取的同时写入 w
io.TeeReader(r Reader, w Writer) Reader- 说明:
- 返回一个 Reader,读取时会同时写入 w
- 类似 Linux tee 命令
- 常用于日志、调试、数据备份
- 返回的 Reader 实现了 io.Reader 接口
- 工作原理:
- 调用 Read 时,先从 r 读取数据
- 然后将读取的数据写入 w
- 最后返回给调用者
- 示例(完整)
package main import ( "bytes" "fmt" "io" "strings" ) func main() { src := strings.NewReader("hello tee") var buf bytes.Buffer // 创建 TeeReader tr := io.TeeReader(src, &buf) // 读取数据(会自动写入 buf) data, _ := io.ReadAll(tr) fmt.Println("读取:", string(data)) fmt.Println("复制:", buf.String()) } - 使用场景示例
- 日志记录
- 示例:
file, _ := os.Create("log.txt") defer file.Close() tr := io.TeeReader(requestBody, file) data, _ := io.ReadAll(tr) // 此时 data 包含原始数据,file 也有备份
- 示例:
- 数据校验
- 示例:
var buf bytes.Buffer tr := io.TeeReader(src, &buf) // 读取并处理 data, _ := io.ReadAll(tr) // 使用 buf 中的数据进行校验 checksum := calculateChecksum(buf.Bytes())
- 示例:
- 调试输出
- 示例:
tr := io.TeeReader(src, os.Stdout) io.ReadAll(tr) // 数据既被读取,又输出到控制台
- 示例:
- 日志记录
- 说明:
🔥 总结
- SectionReader 👉 区间读取
- SeekStart / SeekCurrent / SeekEnd 👉 定位方式
- Seeker 👉 定位接口
👉 写入优化:
- StringWriter 👉 字符串写入
👉 高级流:
- TeeReader 👉 一边读一边写
Go语言标准库 —— io 包(Writer 接口族)
- 写入关闭接口
io.WriteCloser interface
写入 + 关闭接口。-
定义:
type WriteCloser interface { Writer Closer }
-
示例(完整)
package main import ( "fmt" "io" "os" ) func main() { f, err := os.Create("test.txt") if err != nil { return } var wc io.WriteCloser = f wc.Write([]byte("hello")) wc.Close() fmt.Println("写入完成") }
-
- 写入定位接口
io.WriteSeeker interface
写入 + 定位接口。-
定义:
type WriteSeeker interface { Writer Seeker }
-
示例
package main import ( "fmt" "os" ) func main() { f, _ := os.Create("file.txt") defer f.Close() f.Write([]byte("hello")) f.Seek(0, 0) f.Write([]byte("H")) fmt.Println("覆盖写入完成") }
-
- 写入字符串(函数)
向 Writer 写入字符串
io.WriteString(w Writer, s string) (n int, err error)- 说明:
- 将字符串写入到 Writer
- 如果 Writer 实现了 io.StringWriter,会直接调用 WriteString(避免 UTF-8 解码)
- 否则会将字符串转换为 []byte 再调用 Write
- 性能优化:
- 对于实现了 io.StringWriter 的类型(如 bytes.Buffer),性能更好
- 避免了 string -> []byte 的转换和内存分配
- 示例(完整)
package main import ( "fmt" "io" "os" ) func main() { // 写入到标准输出 io.WriteString(os.Stdout, "hello io\n") // 写入到文件 file, _ := os.Create("test.txt") defer file.Close() io.WriteString(file, "file content\n") // 写入到 Buffer var buf bytes.Buffer io.WriteString(&buf, "buffer content") fmt.Println(buf.String()) } - 使用场景示例
- 写入多行文本
- 示例:
var buf bytes.Buffer io.WriteString(&buf, "line1\n") io.WriteString(&buf, "line2\n") io.WriteString(&buf, "line3\n")
- 示例:
- HTTP 响应写入
- 示例:
w := http.ResponseWriter io.WriteString(w, "HTTP/1.1 200 OK\r\n") io.WriteString(w, "Content-Type: text/html\r\n") io.WriteString(w, "\r\n") io.WriteString(w, "<html>Hello</html>")
- 示例:
- 条件写入
- 示例:
if debug { io.WriteString(os.Stderr, "Debug info...\n") }
- 示例:
- 写入多行文本
- 说明:
- 写入接口(核心)
io.Writer interface
最基础写入接口。-
定义:
type Writer interface { Write(p []byte) (n int, err error) }
-
示例
var w io.Writer = os.Stdout w.Write([]byte("hello writer\n"))
-
- 随机写入接口
io.WriterAt interface
支持指定位置写入。-
定义:
type WriterAt interface { WriteAt(p []byte, off int64) (n int, err error) }
-
示例
f, _ := os.Create("file.txt") defer f.Close() f.WriteAt([]byte("hello"), 5) fmt.Println("随机写入完成")
-
- WriterTo 接口
io.WriterTo interface
可将自身写入到 Writer。-
定义:
type WriterTo interface { WriteTo(w Writer) (n int64, err error) }
-
说明:
- io.Copy 会优先使用该接口优化
-
示例
r := strings.NewReader("hello") r.WriteTo(os.Stdout)
-
🔥 总结
- Writer 👉 核心写入接口
- WriteCloser 👉 写入 + 关闭
- WriteSeeker 👉 写入 + 定位
- WriterAt 👉 随机写入
👉 工具函数:
- WriteString 👉 写字符串(优化)
👉 高级接口:
- WriterTo 👉 主动写出(优化 io.Copy)