Std io
Go 语言的 io 标准库提供了一组基本的接口和函数,用于 I/O 操作。以下是 io 包的一些主要 API 及其释义和代码示例。
函数
func Copy(dst Writer, src Reader) (written int64, err error)
将 src 中的数据复制到 dst,直到 src 到达 EOF 或发生错误。返回复制的字节数和遇到的第一个错误(如果有)。
package main
import (
"fmt"
"io"
"strings"
)
func main() {
src := strings.NewReader("Hello, World!")
dst := &strings.Builder{}
written, err := io.Copy(dst, src)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Copied %d bytes: %s\n", written, dst.String())
}
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)
类似 Copy,但使用提供的缓冲区 buf。如果 buf 为 nil,则行为与 Copy 相同。
package main
import (
"fmt"
"io"
"strings"
)
func main() {
src := strings.NewReader("Hello, World!")
dst := &strings.Builder{}
buf := make([]byte, 8) // 自定义缓冲区
written, err := io.CopyBuffer(dst, src, buf)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Copied %d bytes: %s\n", written, dst.String())
}
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)
从 src 复制 n 字节到 dst。如果 src 在复制 n 字节之前到达 EOF,则返回 EOF。
package main
import (
"fmt"
"io"
"strings"
)
func main() {
src := strings.NewReader("Hello, World!")
dst := &strings.Builder{}
written, err := io.CopyN(dst, src, 5)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Copied %d bytes: %s\n", written, dst.String())
}
func Pipe() (*PipeReader, *PipeWriter)
创建一个内存管道,返回一对 PipeReader 和 PipeWriter。
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r, w := io.Pipe()
go func() {
defer w.Close()
w.Write([]byte("Hello, World!"))
}()
buf := make([]byte, 13)
n, err := r.Read(buf)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Read %d bytes: %s\n", n, string(buf))
}
func ReadAll(r Reader) ([]byte, error)
读取 r 直到 EOF 并返回读取的数据。
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("Hello, World!")
data, err := io.ReadAll(r)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Read data: %s\n", string(data))
}
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)
从 r 读取数据到 buf 中,直到至少读取 min 字节。返回读取的字节数和遇到的第一个错误(如果有)。
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("Hello, World!")
buf := make([]byte, 8)
n, err := io.ReadAtLeast(r, buf, 5)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Read %d bytes: %s\n", n, string(buf[:n]))
}
func ReadFull(r Reader, buf []byte) (n int, err error)
从 r 读取 len(buf) 字节到 buf 中。返回读取的字节数和遇到的第一个错误(如果有)。
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("Hello, World!")
buf := make([]byte, 5)
n, err := io.ReadFull(r, buf)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Read %d bytes: %s\n", n, string(buf))
}
func WriteString(w Writer, s string) (n int, err error)
将字符串 s 写入 w。
package main
import (
"fmt"
"io"
"strings"
)
func main() {
var sb strings.Builder
n, err := io.WriteString(&sb, "Hello, World!")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Wrote %d bytes: %s\n", n, sb.String())
}
类型
type ByteReader
一个接口,表示可以读取单个字节的 Reader。
type ByteReader interface {
ReadByte() (byte, error)
}
type ByteScanner
一个接口,表示可以读取和撤销单个字节的 Reader。
type ByteScanner interface {
ByteReader
UnreadByte() error
}
type ByteWriter
一个接口,表示可以写入单个字节的 Writer。
type ByteWriter interface {
WriteByte(c byte) error
}
type Closer
一个接口,表示可以关闭的对象。
type Closer interface {
Close() error
}
type LimitedReader
一个结构体,限制 Reader 读取的最大字节数。
type LimitedReader struct {
R Reader // underlying reader
N int64 // max bytes remaining
}
func (l *LimitedReader) Read(p []byte) (n int, err error)
type OffsetWriter
一个结构体,表示从特定偏移量开始写入的 Writer。
type OffsetWriter struct {
// 内部字段
}
func NewOffsetWriter(w WriterAt, off int64) *OffsetWriter
func (o *OffsetWriter) Seek(offset int64, whence int) (int64, error)
func (o *OffsetWriter) Write(p []byte) (n int, err error)
func (o *OffsetWriter) WriteAt(p []byte, off int64) (n int, err error)
type PipeReader
管道的读取端。
type PipeReader struct {
// 内部字段
}
func (r *PipeReader) Close() error
func (r *PipeReader) CloseWithError(err error) error
func (r *PipeReader) Read(data []byte) (n int, err error)
type PipeWriter
管道的写入端。
type PipeWriter struct {
// 内部字段
}
func (w *PipeWriter) Close() error
func (w *PipeWriter) CloseWithError(err error) error
func (w *PipeWriter) Write(data []byte) (n int, err error)
type ReadCloser
一个接口,组合了 Reader 和 Closer。
type ReadCloser interface {
Reader
Closer
}
func NopCloser(r Reader) ReadCloser
type ReadSeekCloser
一个接口,组合了 Reader、Seeker 和 Closer。
type ReadSeekCloser interface {
Reader
Seeker
Closer
}
type ReadSeeker
一个接口,组合了 Reader 和 Seeker。
type ReadSeeker interface {
Reader
Seeker
}
type ReadWriteCloser
一个接口,组合了 Reader、Writer 和 Closer。
type ReadWriteCloser interface {
Reader
Writer
Closer
}
type ReadWriteSeeker
一个接口,组合了 Reader、Writer 和 Seeker。
type ReadWriteSeeker interface {
Reader
Writer
Seeker
}
type ReadWriter
一个接口,组合了 Reader 和 Writer。
type ReadWriter interface {
Reader
Writer
}
type Reader
一个接口,表示可以读取数据的对象。
type Reader interface {
Read(p []byte) (n int, err error)
}
func LimitReader(r Reader, n int64) Reader
func MultiReader(readers ...Reader) Reader
func TeeReader(r Reader, w Writer) Reader
type ReaderAt
一个接口,表示可以从特定偏移量读取数据的对象。
type ReaderAt interface {
ReadAt(p []byte, off int64) (n int, err error)
}
type ReaderFrom
一个 接口,表示可以从 Reader 中读取数据的对象。
type ReaderFrom interface {
ReadFrom(r Reader) (n int64, err error)
}
type RuneReader
一个接口,表示可以读取单个 Unicode 码点的 Reader。
type RuneReader interface {
ReadRune() (r rune, size int, err error)
}
type RuneScanner
一个接口,表示可以读取和撤销单个 Unicode 码点的 Reader。
type RuneScanner interface {
RuneReader
UnreadRune() error
}
type SectionReader
一个结构体,表示从特定偏移量开始读取的 Reader。
type SectionReader struct {
// 内部字段
}
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader
func (s *SectionReader) Outer() (r ReaderAt, off int64, n int64)
func (s *SectionReader) Read(p []byte) (n int, err error)
func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error)
func (s *SectionReader) Seek(offset int64, whence int) (int64, error)
func (s *SectionReader) Size() int64
type Seeker
一个接口,表示可以设置读取位置的对象。
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
type StringWriter
一个接口,表示可以写入字符串的 Writer。
type StringWriter interface {
WriteString(s string) (n int, err error)
}
type WriteCloser
一个接口,组合了 Writer 和 Closer。
type WriteCloser interface {
Writer
Closer
}
type WriteSeeker
一个接口,组合了 Writer 和 Seeker。
type WriteSeeker interface {
Writer
Seeker
}
type Writer
一个接口,表示可以写入数据的对象。
type Writer interface {
Write(p []byte) (n int, err error)
}
func MultiWriter(writers ...Writer) Writer
type WriterAt
一个接口,表示可以从特定偏移量写入数据的对象。
type WriterAt interface {
WriteAt(p []byte, off int64) (n int, err error)
}
type WriterTo
一个接口,表示可以将数据写入 Writer 的对象。
type WriterTo interface {
WriteTo(w Writer) (n int64, err error)
}
这些是 io 包的主要 API 和类型。通过这些接口和函数,开发者可以方便地进行各种 I/O 操作。
我理解 buf 缓冲区,每满一次,执行一次,为什么上述代码没看到循环?
在 Go 语言的 io 包中,像 io.Copy, io.CopyBuffer, io.CopyN 这样的函数内部确实使用了缓冲区,并在缓冲区填满或读取完成之前会多次调用读取和写入操作。虽然在示例代码中没有显式的循环,但这些函数内部实现了循环逻辑。
以下是 io.Copy 函数的简化版本,展示了内部是如何使用循环的:
package main
import (
"bytes"
"fmt"
"io"
)
func main() {
src := bytes.NewReader([]byte("Hello, World!"))
dst := &bytes.Buffer{}
written, err := Copy(dst, src)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Copied %d bytes: %s\n", written, dst.String())
}
// Copy is a simplified version of io.Copy
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
buf := make([]byte, 32*1024) // 32KB 缓冲区
for {
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if nw > 0 {
written += int64(nw)
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
}
if er != nil {
if er != io.EOF {
err = er
}
break
}
}
return written, err
}
在这个简化版本中,你可以看到 Copy 函数中有一个 for 循环:
- 每次循环中,调用
src.Read(buf)尝试从src读取数据到缓冲区buf。 - 如果读取到的数据长度
nr大于 0,则将这些数据写入dst。 - 如果写入过程中发生错误或读取到的字节数与写入的字节数不一致,则退出循环并返回错误。
- 如果读取过程中遇到
io.EOF,则表示读取完毕,退 出循环。
因此,虽然在使用这些函数时没有显式的循环,但它们内部确实是通过循环来实现数据的读取和写入的。这样可以简化用户代码,使用户不必自己处理循环逻辑。
example
package main
import (
"fmt"
"io"
"strings"
)
func main() {
// 创建一个 strings.Reader
r := strings.NewReader("Hello, World!")
fmt.Println(r) // &{Hello, World! 0 -1}
// 创建一个缓冲区
buf := make([]byte, 5)
// 从 strings.Reader 中读取数据
n, err := r.Read(buf)
if err != nil && err != io.EOF {
fmt.Println("Error:", err)
return
}
fmt.Printf("Read %d bytes: %s\n", n, string(buf[:n]))
// 使用 Seek 方法将读取位置移动到开头
r.Seek(0, io.SeekStart)
// 再次读取数据
n, err = r.Read(buf)
if err != nil && err != io.EOF {
fmt.Println("Error:", err)
return
}
fmt.Printf("Read %d bytes: %s\n", n, string(buf[:n]))
}
&{Hello, World! 0 -1}
在 Go 语言中,strings.Reader 是一个结构体类型,它的定义如下:
type Reader struct {
s string
i int64 // current reading index
prevRune int // index of previous rune; or < 0
}
当你使用 strings.NewReader 创建一个新的 strings.Reader 时,它会初始化上述字段。让我们详细解释这些字段的含义:
-
s: 这是传递给NewReader的字符串。在你的例子中,它的值是"Hello, World!"。 -
i: 这是当前读取的索引,表示下一个要读取的字节位置。初始化时,它的值是0,表示从字符串的开头开始读取。 -
prevRune: 这是前一个读取的 Unicode 码点的索引。如果没有读取任何 Unicode 码点或上一次读取操作不是读取 Unicode 码点(如Read方法),它的值是-1。初始化时,它的值是-1。
所以,当你打印 strings.Reader 的实例时,输出 &{Hello, World! 0 -1} 表示:
s字段是"Hello, World!"。i字段是0,表示当前读取位置在字符串的开头。prevRune字段是-1,表示还没有读取任何 Unicode 码点。
示例代码
以下代码演示了如何读取数据并查看这些字段的变化:
package main
import (
"fmt"
"strings"
)
func main() {
r := strings.NewReader("Hello, World!")
fmt.Println(r) // &{Hello, World! 0 -1}
buf := make([]byte, 5)
r.Read(buf)
fmt.Println(r) // &{Hello, World! 5 -1}
r.Read(buf)
fmt.Println(r) // &{Hello, World! 10 -1}
r.ReadByte()
fmt.Println(r) // &{Hello, World! 11 -1}
r.ReadRune()
fmt.Println(r) // &{Hello, World! 12 11}
}
解释
- 初始状态:
&{Hello, World! 0 -1}。 - 第一次读取 5 个字节后:
&{Hello, World! 5 -1},i增加到 5。 - 第二次读取 5 个字节后:
&{Hello, World! 10 -1},i增加到 10。 - 使用
ReadByte读取一个字节后:&{Hello, World! 11 -1},i增加到 11。 - 使用
ReadRune读取一个 Unicode 码点后:&{Hello, World! 12 11},i增加到 12,prevRune更新为 11。
通过这些操作,你可以看到 strings.Reader 的内部状态是如何随着读取操作而变化的。
读数据到缓冲区
- 每一都是从缓冲区头到缓冲区结束去覆盖,最后一次覆盖到 n 位置,n 之后的内容为倒数第二次未被覆盖的内容,这部分内容是无效的
在 Go 中使用 io.Reader 接口的 Read 方法时,缓冲区(buf)的管理是由调用者负责的。Read 方法不会自动清理缓冲区或重新分配缓冲区。每次调用 Read 方法时,数据会被写入到提供的缓冲区中,并覆盖缓冲区中的现有数据。因此,调用者需要确保在每次读取之前正确管理缓冲区的内容。
示例代码
以下代码演示了如何在缓冲区读满后继续读取,并手动管理缓冲区内容:
package main
import (
"fmt"
"io"
"strings"
)
func main() {
// 创建一个 strings.Reader
r := strings.NewReader("Hello, World!")
// 创建一个缓冲区,长度为 5
buf := make([]byte, 5)
for {
// 从 strings.Reader 中读取数据到缓冲区 buf 中
n, err := r.Read(buf)
if err != nil && err != io.EOF {
fmt.Println("Error:", err)
return
}
// 输出读取的字节数和缓冲区内容
fmt.Printf("Read %d bytes: %s\n", n, string(buf[:n]))
// 如果读取到达输入的末尾,退出循环
if err == io.EOF {
break
}
}
}
解释
- 创建
strings.Reader:r := strings.NewReader("Hello, World!")创建一个新的strings.Reader,包含字符串"Hello, World!"。 - 创建缓冲区:
buf := make([]byte, 5)创建一个长度为 5 的字节切片buf。 - 循环读取数据:
n, err := r.Read(buf)从strings.Reader中读取数据,并将其填充到缓冲区buf中。n是实际读取的字节数。err表示读取过程中是否发生了错误。
- 输出读取结果:
fmt.Printf("Read %d bytes: %s\n", n, string(buf[:n]))打印读取的字节数和缓冲区的内容。 - 检查是否到达输入末尾: 如果
err == io.EOF,表示读取到达了输入的末尾,退出循环。
运行结果
运行上述代码,输出结果如下:
Read 5 bytes: Hello
Read 5 bytes: , Wor
Read 3 bytes: ld!
可以看到,每次 Read 方法会尽可能多地读取数据填充缓冲区,直到缓冲区满或没有更多数据可读取。
注意事项
- 缓冲区管理: 在每次调用
Read方法时,缓冲区中的数据会被新读取的数据覆盖。因此,调用者需要在每次读取之后及时处理缓冲区中的数据。 - 缓冲区大小: 缓冲区的大小决定了每次读取的最大数据量。如果缓冲区太小,可能需要多次读取才能获取所有数据;如果缓冲区太大,可能会浪费内存。
- 错误处理: 在读取过程中,需要正确处理可能发生的错误,例如
io.EOF表示读取到达了输入的末尾。
通过这种方式,你可以灵活地管理缓冲区的内容,并逐步读取大块数据。每次读取操作都会覆盖缓冲区中的数据,因此你需要在读取之后及时处理这些数据。