io 包 - Go 输入输出详解
io 包提供了 I/O 操作的基本接口和工具函数,是 Go 标准库的基石。理解 io.Reader、io.Writer 等核心接口是掌握 Go I/O 编程的关键。
📌 核心概念
📖
Reader
读取数据
Read(p []byte)
✍️
Writer
写入数据
Write(p []byte)
🔗
组合接口
ReaderFrom/WriterTo
io.Copy
🛠️
工具函数
Copy/Limit/TEE
io 包工具
io.Reader 接口
📖 Reader 定义和使用
package main
import (
"fmt"
"io"
"os"
"strings"
)
// io.Reader 接口定义
// type Reader interface {
// Read(p []byte) (n int, err error)
// }
func main() {
// 从字符串读取
reader := strings.NewReader("Hello, World!")
buf := make([]byte, 5)
// 读取数据
n, err := reader.Read(buf)
fmt.Printf("Read %d bytes: %s\n", n, buf[:n])
// Read 返回 io.EOF 表示读取完成
// 读取整个内容
reader2 := strings.NewReader("Hello")
data, _ := io.ReadAll(reader2)
fmt.Printf("All: %s\n", data)
// 读取固定长度
reader3 := strings.NewReader("0123456789")
data2 := make([]byte, 3)
io.ReadFull(reader3, data2) // 必须读满
fmt.Printf("Full: %s\n", data2)
// 读取至少 N 字节
reader4 := strings.NewReader("abc")
buf4 := make([]byte, 10)
n, _ = io.ReadAtLeast(reader4, buf4, 2)
fmt.Printf("AtLeast: %s\n", buf4[:n])
}
💡 Reader 要点
- 返回值: n 为读取字节数,err 为错误
- io.EOF: 读取完成时返回,不是错误
- 阻塞: 没有数据时 Read 会阻塞
- 实现者: *File, *bytes.Buffer, *strings.Reader 等
io.Writer 接口
📖 Writer 定义和使用
package main
import (
"bytes"
"fmt"
"io"
"os"
)
// io.Writer 接口定义
// type Writer interface {
// Write(p []byte) (n int, err error)
// }
func main() {
// 写入 bytes.Buffer
var buf bytes.Buffer
buf.Write([]byte("Hello"))
buf.WriteString(", World!")
fmt.Println(buf.String())
// 写入文件
file, _ := os.Create("test.txt")
defer file.Close()
file.Write([]byte("File content"))
// 多 Writer 合并
var buf1, buf2 bytes.Buffer
multi := io.MultiWriter(&buf1, &buf2)
multi.Write([]byte("Same data"))
fmt.Printf("buf1: %s, buf2: %s\n", buf1.String(), buf2.String())
// 丢弃写入
io.Copy(io.Discard, strings.NewReader("Discarded"))
}
io.Copy 数据拷贝
📝 Copy 和相关函数
package main
import (
"bytes"
"fmt"
"io"
"os"
"strings"
)
func main() {
// 基础 Copy
src := strings.NewReader("Hello, World!")
var dst bytes.Buffer
io.Copy(&dst, src)
fmt.Println(dst.String())
// CopyN: 拷贝 N 字节
src2 := strings.NewReader("0123456789")
var dst2 bytes.Buffer
io.CopyN(&dst2, src2, 5)
fmt.Println(dst2.String()) // 01234
// 文件拷贝
srcFile, _ := os.Open("src.txt")
defer srcFile.Close()
dstFile, _ := os.Create("dst.txt")
defer dstFile.Close()
io.Copy(dstFile, srcFile)
// 带进度拷贝
copyWithProgress()
}
func copyWithProgress() {
var src bytes.Buffer
// 填充大数据...
var dst bytes.Buffer
written, _ := io.Copy(&dst, &src)
fmt.Printf("Copied %d bytes\n", written)
}
io 工具函数
📝 常用工具函数
package main
import (
"bytes"
"fmt"
"io"
"strings"
)
func main() {
// LimitReader: 限制读取 N 字节
src := strings.NewReader("0123456789")
limited := io.LimitReader(src, 5)
data, _ := io.ReadAll(limited)
fmt.Println(string(data)) // 01234
// TeeReader: 读取时复制到另一个 Writer
src2 := strings.NewReader("Hello")
var teeBuf bytes.Buffer
tee := io.TeeReader(src2, &teeBuf)
data2, _ := io.ReadAll(tee)
fmt.Printf("Read: %s, Tee: %s\n", data2, teeBuf.String())
// SectionReader: 读取文件的某一段
// section := io.NewSectionReader(file, offset, size)
// Pipe: 创建管道
pr, pw := io.Pipe()
go func() {
pw.Write([]byte("Pipe data"))
pw.Close()
}()
data3, _ := io.ReadAll(pr)
fmt.Println(string(data3))
}
实现 Reader/Writer
📝 自定义 Reader/Writer
package main
import (
"errors"
"fmt"
"io"
)
// 自定义 Reader: 只返回大写字母
type UpperReader struct {
src io.Reader
}
func (r *UpperReader) Read(p []byte) (int, error) {
n, err := r.src.Read(p)
for i := 0; i < n; i++ {
if p[i] >= 'a' && p[i] <= 'z' {
p[i] -= 32
}
}
return n, err
}
// 自定义 Writer: 统计写入字节数
type CountWriter struct {
Count int64
}
func (c *CountWriter) Write(p []byte) (int, error) {
c.Count += int64(len(p))
return len(p), nil
}
func main() {
// 使用 UpperReader
src := strings.NewReader("hello world")
upper := &UpperReader{src}
data, _ := io.ReadAll(upper)
fmt.Println(string(data)) // HELLO WORLD
// 使用 CountWriter
cw := &CountWriter{}
io.Copy(cw, strings.NewReader("12345"))
fmt.Printf("Written: %d bytes\n", cw.Count)
}
最佳实践
✅ io 包使用建议
- 面向接口: 函数参数使用 io.Reader/Writer
- 缓冲 IO: 大文件使用 bufio 提升性能
- 错误处理: 检查 io.EOF 和其他错误
- 资源清理: 使用 defer 关闭文件
- io.Copy: 优先使用 io.Copy 而非手动循环