io
Go语言中的io
包为I/O操作提供了基础接口和工具,旨在抽象不同底层实现的细节,使代码能够以统一的方式处理多种I/O类型(如文件、内存缓冲区、网络连接等)。
核心接口
Reader
接口:定义:
Read(p []byte) (n int, err error)
作用:从数据源读取数据到字节切片
p
中,返回读取的字节数n
和可能的错误(如io.EOF
表示数据结束)。常见实现:
os.File
(文件)、bytes.Buffer
(内存缓冲区)、net.Conn
(网络连接)。
Writer
接口:定义:
Write(p []byte) (n int, err error)
作用:将字节切片
p
写入目标,返回实际写入的字节数n
和错误。常见实现:同
Reader
的实现类型。
Closer
接口:定义:
Close() error
作用:关闭资源(如文件、网络连接),释放相关系统资源。
组合接口:
ReadWriter
:组合了Reader
和Writer
。ReadCloser
/WriteCloser
:组合了Closer
接口,用于读写后关闭资源。Seeker
:提供Seek(offset int64, whence int) (int64, error)
方法,用于调整读写位置(如随机访问文件)。
1 常量
const (
SeekStart = 0 // seek relative to the origin of the file
SeekCurrent = 1 // seek relative to the current offset
SeekEnd = 2 // seek relative to the end
)
2 变量
2.1 EOF (End Of File)
定义:
var EOF = errors.New("EOF")
用途: 当
Reader
读取到输入流的末尾时返回此错误。它表示正常的数据结束,而不是一个真正的错误。关键特性:
必须直接返回
EOF
自身(而不是用fmt.Errorf
包装),因为调用方通常用==
直接比较错误:if err == io.EOF { // 处理正常结束 }
仅在数据预期结束时返回。例如,读取文件到末尾。
示例场景:
data := make([]byte, 100) n, err := reader.Read(data) if err == io.EOF { fmt.Println("数据读取完毕") }
2.2 ErrUnexpectedEOF
定义:
var ErrUnexpectedEOF = errors.New("unexpected EOF")
用途: 当在读取固定大小的数据块或结构化数据时,未达到预期长度就遇到了
EOF
。这表示数据可能被意外截断。常见场景:
读取协议头(如固定长度的二进制头部)。
解码结构化数据(如 JSON/XML 片段不完整)。
示例:
buf := make([]byte, 1024) if _, err := io.ReadFull(reader, buf); err != nil { if err == io.ErrUnexpectedEOF { fmt.Println("数据不完整") } }
2.3 ErrClosedPipe
定义:
var ErrClosedPipe = errors.New("io: read/write on closed pipe")
用途: 当对已关闭的
Pipe
(管道)执行读/写操作时返回此错误。管道通常用于连接并发代码(如io.Pipe()
)。典型场景:
pr, pw := io.Pipe() pw.Close() // 显式关闭写入端 _, err := pw.Write([]byte("data")) if err == io.ErrClosedPipe { fmt.Println("管道已关闭") }
2.4 ErrShortBuffer
定义:
var ErrShortBuffer = errors.New("short buffer")
用途: 当
Reader
需要读取的数据长度超过了提供的缓冲区容量时返回此错误。常见于:
io.ReadFull
:尝试填充整个缓冲区,但输入流提前结束。
示例:
buf := make([]byte, 5) if _, err := io.ReadFull(reader, buf); err != nil { if err == io.ErrShortBuffer { fmt.Println("缓冲区太小") } }
2.5 ErrShortWrite
定义:
var ErrShortWrite = errors.New("short write")
用途: 当
Writer
实际写入的字节数少于请求的字节数,但未返回明确的错误时触发。这通常表示底层 I/O 资源异常(如磁盘满)。关键点:
与
ErrShortBuffer
不同,此错误发生在写入端。
处理建议:
可能需要重试写入剩余的数据。
示例:
data := []byte("hello") n, err := writer.Write(data) if err == io.ErrShortWrite { fmt.Printf("仅写入 %d 字节\n", n) }
2.6 ErrNoProgress
定义:
var ErrNoProgress = errors.New("multiple Read calls return no data or error")
用途: 当连续多次调用
Reader.Read
均未返回数据或错误时触发。通常表示Reader
的实现存在缺陷(如死循环)。典型场景:
自定义的
Reader
未正确处理读取逻辑。
示例:
type BrokenReader struct{} func (r *BrokenReader) Read(p []byte) (n int, err error) { // 错误实现:永远返回 0, nil return 0, nil } func main() { buf := make([]byte, 10) _, err := io.ReadAtLeast(&BrokenReader{}, buf, 1) if err == io.ErrNoProgress { fmt.Println("Reader 实现异常") } }
错误处理最佳实践
区分
EOF
和ErrUnexpectedEOF
:使用
io.ReadFull
或类似方法读取固定长度数据时,优先检查ErrUnexpectedEOF
。
避免包装
EOF
:// 正确 return 0, io.EOF // 错误(会导致 == 判断失败) return 0, fmt.Errorf("read failed: %w", io.EOF)
3 函数
3.1 func Copy(dst Writer, src Reader) (written int64, err error)
作用: 将数据从
src
读取并写入dst
,直到src
返回io.EOF
或发生错误。内部自动管理缓冲区,默认使用 32KB 的临时缓冲区。返回值: 成功复制的字节数
written
和遇到的第一个错误(如io.EOF
)。 成功的复制返回 err == nil,而不是 err == EOF。由于 Copy 被定义为从 src 读取到 EOF,因此它不会将 Read 中的 EOF 视为要报告的错误。适用场景:
文件拷贝(如从
os.File
复制到另一个文件)。网络数据传输(如将
net.Conn
内容写入bytes.Buffer
)。
示例:
src, _ := os.Open("input.txt") dst, _ := os.Create("output.txt") written, err := io.Copy(dst, src) // 复制文件
3.2 func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)
作用: 与
Copy
功能相同,但允许自定义缓冲区buf
。若buf
为nil
,函数会自行创建默认大小为1的缓冲区。适用场景:
需要复用或控制缓冲区大小的场景(如高频调用时减少内存分配)。
示例:
buf := make([]byte, 64 * 1024) // 64KB 缓冲区 written, err := io.CopyBuffer(dst, src, buf)
3.3 func CopyN(dst Writer, src Reader, n int64) (written int64, err error)
作用: 从
src
复制精确的n
字节到dst
。若src
提前返回io.EOF
,会触发io.ErrUnexpectedEOF
。适用场景:
复制固定长度的数据(如读取协议头或分块传输)。
示例:
// 复制前 100 字节 written, err := io.CopyN(dst, src, 100) if errors.Is(err, io.ErrUnexpectedEOF) { fmt.Println("数据不足 100 字节") }
3.4 func Pipe() (*PipeReader, *PipeWriter)
作用: 创建一对关联的
PipeReader
和PipeWriter
,形成一个内存管道。写入PipeWriter
的数据可直接被PipeReader
读取。特性:
阻塞性:写入操作会阻塞,直到另一端读取数据(反之亦然)。
错误处理:关闭一端后,另一端的读写会返回
io.ErrClosedPipe
。
适用场景:
连接需要分离读写逻辑的代码(如加密/解密流水线)。
多 goroutine 间数据传递。
示例:
pr, pw := io.Pipe() go func() { defer pw.Close() pw.Write([]byte("通过管道传输的数据")) }() data, _ := io.ReadAll(pr) // 读取数据
3.5 func ReadAll(r Reader) ([]byte, error)
作用: 读取
r
的所有数据直到io.EOF
,返回完整字节切片。注意:
内存风险:大文件可能导致内存耗尽,建议改用流式处理(如
io.Copy
)。ReadAll 从 r 读取,直到出现错误或 EOF,并返回它读取的数据。成功的调用返回 err == nil,而不是 err == EOF。由于 ReadAll 定义为从 src 读取到 EOF,因此它不会将 Read 中的 EOF 视为要报告的错误。
适用场景:
读取小型配置文件或 HTTP 响应体。
示例:
data, err := io.ReadAll(resp.Body) // 读取 HTTP 响应
3.6 func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)
作用: 从
r
读取至少min
字节到buf
。若实际读取的字节数< min
,返回io.ErrUnexpectedEOF
(若r
提前结束)或io.ErrShortBuffer
(若buf
太小)。适用场景:
读取必须满足最小长度的数据(如二进制协议解析)。
示例:
buf := make([]byte, 10) n, err := io.ReadAtLeast(reader, buf, 5) // 至少读取 5 字节
3.7 func ReadFull(r Reader, buf []byte) (n int, err error)
作用: 等价于
ReadAtLeast(r, buf, len(buf))
,即必须填满整个buf
。错误:
若未填满且
r
结束,返回io.ErrUnexpectedEOF
。
适用场景:
读取固定长度的数据块(如读取 4 字节的整数)。
示例:
buf := make([]byte, 4) n, err := io.ReadFull(reader, buf) // 必须读取 4 字节
3.8 func WriteString(w Writer, s string) (n int, err error)
作用: 将字符串
s
写入w
。若w
实现了io.StringWriter
接口(如bytes.Buffer
),会直接调用其WriteString
方法以优化性能。优化点:
避免将字符串转换为
[]byte
的额外内存分配。
适用场景:
高效写入字符串内容(如日志输出)。
示例:
var buf bytes.Buffer n, _ := io.WriteString(&buf, "Hello, World!") // 写入字符串
总结与选择建议
Copy
流式复制
自动管理缓冲区,适合大文件或网络流
CopyBuffer
自定义缓冲区复制
控制内存复用
CopyN
复制固定长度
精确控制复制量
Pipe
内存管道通信
阻塞式读写,适用并发场景
ReadAll
读取全部数据
简单但需注意内存风险
ReadAtLeast
读取最小字节
确保最低数据量
ReadFull
填满缓冲区
严格数据完整性
WriteString
高效写入字符串
避免类型转换开销
3.9 func Pipe() (*PipeReader, *PipeWriter)
io.Pipe()
用于在内存中创建一个同步的、无缓冲的管道,允许在两个协程(Goroutine)间直接传递数据流。它返回一对读写接口:*PipeReader
(读端)和 *PipeWriter
(写端),使数据生产者可以写入数据,消费者同时读取数据,且两者操作完全同步。
3.9.1 核心特性
(1) 同步无缓冲
无中间缓存:写入的数据直接从
PipeWriter
传输到PipeReader
,不存储在中间缓冲区。写入阻塞,直至读消费:每次写操作(
Write
)会阻塞,直到所有写入的数据被读取端通过一次或多次Read
调用完全读取。
r, w := io.Pipe()
go func() {
// 写入 "hello" 后阻塞,等待读端消费
w.Write([]byte("hello"))
w.Close() // 可选:关闭写端
}()
buf := make([]byte, 5) // 缓冲区大小为5
n, _ := r.Read(buf) // 读取后,写端解除阻塞
fmt.Println(string(buf[:n])) // 输出 "hello"
(2) 协程安全的并行操作
并发安全:
Read
和Write
可在不同协程并发调用。多个并行的
Read
或Write
会被自动串行化(顺序执行)。
组合关闭操作:可安全并行调用
Close
或CloseWithError
,关闭操作会中断阻塞的读写。
(3) 严格的一对一匹配
多读需消费单次写:如果单次写入的数据量很大,需要多次
Read
调用才能全部消费。
r, w := io.Pipe()
go func() {
w.Write([]byte("1234567890")) // 写入10字节
w.Close()
}()
buf1 := make([]byte, 5)
n1, _ := r.Read(buf1) // 第一次读取5字节("12345")
buf2 := make([]byte, 5)
n2, _ := r.Read(buf2) // 第二次读取5字节("67890")
4 类型
4.1 Reader
type Reader interface {
Read(p []byte) (n int, err error)
}
读取数据
将数据从底层数据源读取到字节切片
p
中。返回实际读取的字节数
n
(0 <= n <= len(p)
)和可能的错误err
。
错误处理
当读取到文件末尾(EOF)时,返回
io.EOF
。即使
n > 0
,也可能在同一个调用中返回错误(如中途遇到错误)。若
len(p) == 0
,必须返回n == 0
,可能直接返回错误(如提前知道EOF)。
注意事项
调用者应先处理
n > 0
的数据,再检查错误。例如,即使返回io.EOF
,也可能已读取部分有效数据。实现时不能保留切片
p
(避免底层数据被意外修改)。
4.1.1 LimitReader:限制读取字节数
函数签名:
func LimitReader(r Reader, n int64) Reader
功能:
创建一个新的
Reader
,从r
读取,但最多读取n
字节后返回EOF
。底层实现:
*LimitedReader
类型。
关键行为:
提前终止:若
r
在达到n
字节前返回EOF
,则LimitReader
也终止。精确截断:读取超过
n
字节的请求会被限制为剩余可读字节数。
示例:
// 只读取文件前 100 字节
file, _ := os.Open("data.txt")
limited := io.LimitReader(file, 100) data, _ := io.ReadAll(limited) // data 长度最多 100
4.1.2 MultiReader:串联多个读取器
函数签名:
func MultiReader(readers ...Reader) Reader
功能:
按顺序串联多个
Reader
,逻辑上等效于依次读取每个Reader
的内容,直到全部返回EOF
。
关键行为:
顺序读取:依次从每个
Reader
读取数据,前一个返回EOF
后切换到下一个。错误传播:若任意
Reader
返回非EOF
错误,整个MultiReader
的Read
返回该错误。EOF 处理:所有
Reader
均返回EOF
后,Read
返回EOF
。
示例:
// 合并两个字符串读取器
r1 := strings.NewReader("Hello, ")
r2 := strings.NewReader("World!")
multi := io.MultiReader(r1, r2) data, _ := io.ReadAll(multi) // "Hello, World!"`
4.1.3 TeeReader:读取时同步写入
函数签名:
func TeeReader(r Reader, w Writer) Reader
功能:
创建一个
Reader
,从r
读取数据时,同步将数据写入w
。无缓冲设计:每次
Read
调用会阻塞直到写入w
完成。错误传递:若写入
w
失败,Read
返回写入错误。
关键行为:
实时性:数据在读取时立即写入,适合需要严格同步的场景(如计算哈希)。
性能影响:写入延迟会拖慢读取速度(可通过缓冲
Writer
优化)。
示例:
// 读取 HTTP 响应并计算
MD5 resp, _ := http.Get("https://example.com")
defer resp.Body.Close()
hasher := md5.New()
tee := io.TeeReader(resp.Body, hasher) // 数据同时写入 hasher
// 读取并处理数据
data, _ := io.ReadAll(tee)
checksum := hasher.Sum(nil) // 获取 MD5 值
4.1.4 对比与总结
函数
用途
关键特性
LimitReader
限制读取字节数
精准截断,底层为 *LimitedReader
MultiReader
串联多个数据源
顺序读取,错误传播机制
TeeReader
读取时同步写入其他目标
无缓冲,写入错误直接传递
适用场景:
LimitReader
:限制文件下载大小、分块读取日志。
MultiReader
:合并多个文件或网络流、实现管道式处理。
TeeReader
:日志记录、实时数据校验(如CRC)、数据备份。
4.2 Writer
type Writer interface {
Write(p []byte) (n int, err error)
}
核心行为:
写入数据:将
p
中的全部数据写入底层数据流,返回实际写入的字节数n
和错误err
。严格约束:
完全写入或错误:若
n < len(p)
,必须返回非nil
错误。禁止修改数据:
Write
不能修改p
的内容,即使临时修改也不允许。禁止保留引用:实现不得保留
p
的引用(防止数据竞争)。
4.2.1 Discard:空写入器
作用:
所有
Write
调用直接成功,实际不执行任何操作。用于忽略输出(如丢弃日志、测试占位符)。
实现:
var Discard Writer = discard{}
type discard struct{}
func (discard) Write(p []byte) (int, error) {
return len(p), nil // 直接返回成功
}
4.2.2 MultiWriter:多路写入器
功能:
将数据同时写入多个
Writer
,类似 Unix 的tee
命令。原子性:遇到第一个错误时立即停止,不保证所有
Writer
都写入数据。
实现逻辑:
func MultiWriter(writers ...Writer) Writer {
return &multiWriter{writers}
}
type multiWriter struct {
writers []Writer
}
func (mw *multiWriter) Write(p []byte) (n int, err error) {
for _, w := range mw.writers {
n, err = w.Write(p)
if err != nil {
return n, err // 遇到错误立即返回
}
if n != len(p)
{
return n, io.ErrShortWrite // 强制检查完整性
}
}
return len(p), nil
}
关键行为:
顺序写入:依次调用每个
Writer
的Write
方法。错误处理:
若某个
Writer
返回错误,整个操作终止并返回该错误。若
Writer
返回n < len(p)
(违反接口约定),强制返回ErrShortWrite
。
无回滚:已写入的
Writer
数据不会撤销(非原子操作)。
4.3 io.ReadWriter
定义:组合
Reader
和Writer
接口。作用:支持同时读写操作,常见于文件或网络连接。
实现类型:
os.File
、net.Conn
等。
4.4 io.ReadCloser 和 io.WriteCloser
定义:组合
Reader
/Writer
与Closer
接口。作用:支持读写后关闭资源,常见于需要资源管理的场景。
实现类型:
http.Response.Body
(ReadCloser
)、压缩包的读写器等。
4.4.1 NopCloser 函数
4.4.1.1 定义
func NopCloser(r Reader) ReadCloser
功能:将一个
Reader
包装为ReadCloser
,其Close
方法为空操作(no-op)。适用场景:当数据源不需要关闭,但需要满足
ReadCloser
接口时(如内存缓冲区)。
4.4.1.2 底层实现
空关闭逻辑:
Close()
方法直接返回nil
。保留优化:若原始
Reader
实现了WriterTo
接口,包装后的对象也会实现WriterTo
,以支持高效写入(如io.Copy
优先调用WriteTo
)。
使用示例
// 将内存中的 Reader 包装为 ReadCloser
r := bytes.NewReader([]byte("hello"))
rc := io.NopCloser(r)
defer rc.Close() // 无实际效果,但避免资源泄漏检查工具误报
// 传递给需要 ReadCloser 的 API
data, _ := io.ReadAll(rc)
fmt.Println(string(data)) // 输出 "hello"
4.4.1.3 关键注意事项
(1) 资源泄漏风险
正确使用场景:仅对 无需关闭 的
Reader
使用NopCloser
(如bytes.Reader
)。错误用法:若原始
Reader
需要关闭(如os.File
),应直接使用其本身的Close
方法,而非用NopCloser
包装。
(2) 性能优化
WriterTo
透传:
// 假设 r 实现了 WriterTo(如 bytes.Reader)
rc := io.NopCloser(r) var buf bytes.Buffer io.Copy(&buf, rc)
// 调用 r 的 WriteTo 方法,而非逐字节复制`
4.5 io.Closer
定义:
Close() error
作用:关闭资源(如文件、网络连接),释放系统资源。
重要性:
资源管理的关键接口,避免内存泄漏或文件句柄耗尽。
通常与
Reader
或Writer
组合使用(如io.ReadCloser
)。
典型场景:
file, _ := os.Open("data.txt") defer file.Close() // 确保文件关闭
4.6 Seeker
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
功能:设置下一次读写操作的起始偏移量。
参数:
offset
:偏移量(可为正或负)。whence
:基准位置,支持三种模式:io.SeekStart
(0):相对于文件开头。io.SeekCurrent
(1):相对于当前偏移量。io.SeekEnd
(2):相对于文件末尾。
返回值:新的偏移量(相对于文件开头)和错误。
操作
结果
Seek
到文件开头之前 (offset < 0
)
返回错误(如 os.ErrInvalid
)。
Seek
超过文件末尾
允许,但后续写入可能扩展文件,读取返回 EOF
(依赖具体实现)。
组合 Read
和 Seek
形成 ReadSeeker
接口,支持读取时动态调整位置。
file, _ := os.Open("data.bin")
defer file.Close()
// 跳转到第100字节处读取
offset, _ := file.Seek(100, io.SeekStart)
buf := make([]byte, 50)
n, _ := file.Read(buf) // 读取 100-149 字节
fmt.Printf("Read %d bytes from offset %d\n", n, offset)
4.7 WriterAt与ReaderAt
WriterAt接口
type WriterAt interface {
WriteAt(p []byte, off int64) (n int, err error)
}
功能:在底层数据流的指定偏移量
off
处写入字节切片p
。返回值:
n
:实际写入的字节数(0 ≤ n ≤ len(p)
)。err
:若n < len(p)
,必须返回非nil
错误(如磁盘满、权限不足)。
并发性:允许并发的
WriteAt
调用,只要写入范围不重叠。独立性:操作不影响底层流的位置(如文件的当前偏移量)。
ReaderAt 接口
type ReaderAt interface {
ReadAt(p []byte, off int64) (n int, err error)
}
功能:从底层数据流的指定偏移量
off
处读取数据到p
。返回值:
n
:实际读取的字节数(0 ≤ n ≤ len(p)
)。err
:若n < len(p)
,必须返回非nil
错误(如EOF
或读取错误)。
严格性:比
Read
更严格,若数据不足,会阻塞直到填满p
或遇到错误。并发性:允许并发的
ReadAt
调用,无需考虑重叠。
与顺序读写接口的对比
接口
操作方式
影响偏移量
并发支持
典型场景
Writer
顺序写入
更新偏移量
需外部同步
日志追加、网络流发送
WriterAt
随机写入
不影响偏移量
支持非重叠并发写入
文件随机修改、内存操作
Reader
顺序读取
更新偏移量
需外部同步
流式读取(如 HTTP 响应)
ReaderAt
随机读取
不影响偏移量
支持任意并发读取
数据库索引、二进制解析
4.7.1 实现要求与注意事项
WriterAt 实现
原子性:并发写入不重叠区域时,需保证数据完整性(如文件系统块锁)。
错误处理:部分写入必须返回错误(如写入磁盘时空间不足)。
示例实现:
type MemoryWriter struct {
data []byte
mu sync.Mutex
}
func (m *MemoryWriter) WriteAt(p []byte, off int64) (int, error) {
m.mu.Lock()
defer m.mu.Unlock()
end := off + int64(len(p))
if end > int64(len(m.data)) {
m.data = append(m.data, make([]byte, end-int64(len(m.data)))...)
}
copy(m.data[off:], p)
return len(p), nil
}
ReaderAt 实现
阻塞行为:若数据不足,需等待数据到达或返回错误(如网络流读取)。
示例实现:
type BlockingReader struct {
data []byte
cond *sync.Cond
closed bool
}
func (b *BlockingReader) ReadAt(p []byte, off int64) (n int, err error) {
b.cond.L.Lock()
defer b.cond.L.Unlock()
for !b.closed && off >= int64(len(b.data)) {
b.cond.Wait() // 等待数据写入或关闭
}
if b.closed {
return 0, io.EOF
}
n = copy(p, b.data[off:])
return n, nil
}
WriterAt 应用
分块写入文件:多线程下载文件时,各线程写入不同区域。
file, _ := os.Create("largefile.iso")
defer file.Close()
var wg sync.WaitGroup
for i := 0; i < 4; i++ {
wg.Add(1)
go func(offset int64) {
defer wg.Done()
data := fetchChunk(offset)
file.WriteAt(data, offset)
}(int64(i) * chunkSize)
}
wg.Wait()
内存数据库操作:直接修改内存中的键值对存储。
type DB struct {
buffer []byte
mu sync.RWMutex
}
func (db *DB) Update(key string, value []byte) error {
db.mu.Lock()
defer db.mu.Unlock()
offset := getOffset(key) // 计算键的存储位置
_, err := db.WriteAt(value, offset)
return err
}
ReaderAt 应用
随机访问二进制文件:解析 ELF 文件头或 ZIP 目录。
file, _ := os.Open("binary.exe")
defer file.Close()
header := make([]byte, 16)
file.ReadAt(header, 0) // 读取文件头部
magic := string(header[:4])
fmt.Println("Magic number:", magic)
并发读取日志分析:多个协程并行分析日志的不同部分。
func analyzeSegment(r ReaderAt, offset, length int64) {
buf := make([]byte, length)
r.ReadAt(buf, offset)
// 处理 buf 中的日志段
}
logFile, _ := os.Open("server.log")
defer logFile.Close()
go analyzeSegment(logFile, 0, 1000)
go analyzeSegment(logFile, 1000, 1000)
4.7.2 常见问题解答
Q1:
ReadAt
和Read
的阻塞行为有何不同?
Read
:可能立即返回可用数据(即使不足len(p)
),不保证填满p
。
ReadAt
:必须等待len(p)
字节可用或遇到错误,若数据不足会阻塞。
Q2:如何保证
WriterAt
的并发安全?
非重叠写入:由调用方确保写入区域不重叠(如分块下载)。
内部同步:若实现可能被并发调用,需使用锁或原子操作保护共享状态。
Q3:
WriteAt
写入超出文件末尾时会发生什么?
文件扩展:大多数系统(如
os.File
)会自动扩展文件并用零填充空隙。示例:
file.WriteAt([]byte("end"), 100) // 若文件原长 50 字节,扩展至 103 字节
4.8 PipeReader与PipeWriter
io.Pipe
提供了一种 无缓冲的同步内存管道,用于在两个 Go 例程之间直接传递数据。PipeReader
和 PipeWriter
是管道的两端,通过这种方式实现数据的生产者和消费者模型。
4.8.1 核心特性
同步阻塞:写入操作会阻塞,直到有读者读取数据;读取操作也会阻塞,直到写入者提供数据或管道关闭。
无缓冲设计:数据直接从写端传递到读端,无需中间缓存。
线程安全:允许多个协程并发操作管道的读端或写端(但通常建议单读单写)。
4.8.2 PipeReader 方法
4.8.2.1 Close()
func (r *PipeReader) Close() error
功能:关闭读端。后续的写操作会返回
io.ErrClosedPipe
。典型用途:通知写端停止写入(如提前终止数据传输)。
r, w := io.Pipe() go func() {
defer r.Close() // 读者关闭读端,终止写端操作
}()
4.8.2.2 CloseWithError(err error)
func (r *PipeReader) CloseWithError(err error) error
功能:关闭读端并指定错误。后续写操作返回这个错误。
幂等性:多次调用不会覆盖前一次的错误。
示例:
r.CloseWithError(fmt.Errorf("custom error")) // 写操作的 Write 返回 "custom error"`
4.8.2.3 Read(data []byte)
func (r *PipeReader) Read(data []byte) (n int, err error)
行为:
若无数据且写端未关闭,阻塞 直到数据写入或写端关闭。
若写端正常关闭,返回
io.EOF
。若写端通过
CloseWithError
关闭,返回指定的错误。
示例:
data := make([]byte, 100) n, err := r.Read(data) if err != nil {
fmt.Println("读取错误:", err)
}
4.8.3 PipeWriter 方法
4.8.3.1 Close()
func (w *PipeWriter) Close() error
功能:关闭写端。后续读操作可能读取到残留数据,之后返回
io.EOF
。示例:
defer w.Close() if _, err := w.Write([]byte("hello")); err != nil {
log.Fatal(err)
}
4.8.3.2 CloseWithError(err error)
func (w *PipeWriter) CloseWithError(err error) error
功能:关闭写端并传递错误。读端在读取完已有数据后,会收到这个错误。
注意:多次调用不会覆盖之前的错误。
4.8.3.3 Write(data []byte)
func (w *PipeWriter) Write(data []byte) (n int, err error)
行为:
若无读者或读端已关闭,返回
io.ErrClosedPipe
。写入数据时阻塞,直到数据被读取或读端关闭。
4.8.3.4 使用场景
场景1:流式处理
r, w := io.Pipe()
// 生产者写入数据
go func() {
defer w.Close()
for i := 0; i < 5; i++ {
fmt.Fprintf(w, "data chunk %d\n", i)
}
}()
// 消费者读取数据
go func() {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
fmt.Println("Received:", scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Println("Read error:", err)
}
}()
time.Sleep(time.Second) // 等待协程完成
场景2:中间件代理
func CopyWithTransform(input io.Reader) io.Reader {
r, w := io.Pipe()
go func() {
defer w.Close()
// 处理输入数据并写入管道
scanner := bufio.NewScanner(input)
for scanner.Scan() {
transformed := strings.ToUpper(scanner.Text())
fmt.Fprintln(w, transformed)
}
if err := scanner.Err(); err != nil {
w.CloseWithError(err)
}
}()
return r
}
4.8.3.5 常见错误与防范
错误1:未关闭管道
现象:协程永久阻塞,导致内存泄漏。
解决:确保至少关闭一端:
defer func() {
w.Close() r.Close()
}()
错误2:协程未等待
现象:主协程退出,导致子协程未执行完毕。
解决:使用
sync.WaitGroup
或context.Context
同步。
错误3:并发读写
现象:数据竞态或非预期错误。
解决:通常设计为单生产者单消费者模式。
4.8.4 io.WriterTo 接口
io.WriterTo
是一个标准接口,定义如下:
type WriterTo interface {
WriteTo(w Writer) (n int64, err error)
}
功能:将数据从调用者(实现该接口的对象)直接写入
w
,无需通过中间缓冲。典型实现:
os.File
、bytes.Buffer
、net.TCPConn
等类型可能已实现此接口。
最后更新于