Menu
CoddyTech

io.Reader 与 io.Writer

本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 73 课,共 107 课。

Go 的标准库通过两个基本接口展示了基于接口的设计:io.Reader 和 io.Writer。这些简单的接口支撑着从文件操作到网络通信的各种功能。

io.Reader 接口只要求实现一个方法:

type Reader interface {
    Read(p []byte) (n int, err error)
}

它最多将 len(p) 个字节读入切片,并返回已读取的字节数。类似地,io.Writer 定义了:

type Writer interface {
    Write(p []byte) (n int, err error)
}

实现这些方法的任何类型都能与整个 I/O 生态系统配合使用。

这是一个实现了 io.Reader 的自定义类型:

type RepeatReader struct {
    Char  byte
    Count int
    pos   int
}

func (r *RepeatReader) Read(p []byte) (int, error) {
    if r.pos >= r.Count {
        return 0, io.EOF
    }
    n := 0
    for n < len(p) && r.pos < r.Count {
        p[n] = r.Char
        n++
        r.pos++
    }
    return n, nil
}

现在,这个自定义读取器可以与任何需要 io.Reader 的函数配合使用:

reader := &RepeatReader{Char: 'A', Count: 5}
data, _ := io.ReadAll(reader)
fmt.Println(string(data))  // AAAAA

这种设计让你可以编写接收 io.Reader 或 io.Writer 的函数,使它们能够处理文件、网络连接、缓冲区或任何自定义实现。

challenge icon

挑战

简单

一起来构建一个自定义文本流处理器,实现 io.Reader 和 io.Writer 接口吧!你将创建一些类型,在文本流经它们时对文本进行转换,展示这些基础接口如何实现强大的 I/O 组合。

你需要将代码分布在两个文件中:

  • streams.go:定义自定义的读取器和写入器类型。

    创建一个 CountingReader 结构体,它包装一个字符串,并跟踪已读取的字节数。它应该有用于存储源字符串和跟踪位置的字段。实现 Read(p []byte) (int, error) 方法,从源中读取字节并放入提供的切片,更新位置,并在源数据耗尽时返回 io.EOF。

    创建一个 UppercaseWriter 结构体,它会收集写入的数据并将其转换为大写。它应该在内部存储累积的结果。实现 Write(p []byte) (int, error) 方法,将传入的字节转换为大写并存储。添加一个 Result() string 方法,用于获取累积的大写文本。

    创建构造函数 NewCountingReader(source string) *CountingReader 和 NewUppercaseWriter() *UppercaseWriter,以正确初始化这些类型。

  • main.go:演示如何让你的自定义 I/O 类型与标准库配合使用。

    读取一个字符串输入,然后使用 CountingReader 配合 io.ReadAll 读取所有数据。通过 UppercaseWriter 的 Write 方法,将这些数据传入其中。

    按以下格式输出结果:

    Original: [input]
    Uppercase: [result from writer]
    Bytes read: [total bytes]

将提供以下输入:

  • 一行文本

例如,给定:

Hello World

你的输出应为:

Original: Hello World
Uppercase: HELLO WORLD
Bytes read: 11

再例如,给定:

Go interfaces are powerful

你的输出应为:

Original: Go interfaces are powerful
Uppercase: GO INTERFACES ARE POWERFUL
Bytes read: 26

关键在于,CountingReader 实现了 io.Reader,因此可以与 io.ReadAll 无缝配合。在 Go 生态中,任何接受 io.Reader 的函数都能与你的自定义类型配合使用,这正是基于接口设计的强大之处。

亲手试试

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	// 读取输入
	reader := bufio.NewReader(os.Stdin)
	input, _ := reader.ReadString('\n')
	// 如果末尾有换行符,就将其删除
	if len(input) > 0 && input[len(input)-1] == '\n' {
		input = input[:len(input)-1]
	}

	// TODO: 用输入字符串创建 CountingReader
	// countingReader := NewCountingReader(input)

	// TODO: 使用 io.ReadAll 读取你的 CountingReader 中的所有数据
	// data, err := io.ReadAll(countingReader)

	// TODO: 创建 UppercaseWriter
	// upperWriter := NewUppercaseWriter()

	// TODO: 将数据写入你的 UppercaseWriter
	// upperWriter.Write(data)

	// TODO: 按要求的格式打印结果:
	// 原文:[input]
	// 大写:[writer 的结果]
	// 已读取字节数:[总字节数]

	// 占位代码,用于避免未使用的导入错误;实现时请删除
	_ = io.EOF
	fmt.Println("Original:", input)
	// fmt.Println("Uppercase:", upperWriter.Result())
	// fmt.Println("Bytes read:", countingReader.Position)
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Go 在线编译器