Menu
Coddy logo textTech

io.Reader と io.Writer

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 73/107。

Goの標準ライブラリは、2つの基本的なインターフェースである io.Readerio.Writer を通じて、インターフェースベースの設計を体現しています。これらのシンプルなインターフェースは、ファイル操作からネットワーク通信に至るまで、あらゆる機能を支えています。

io.Reader インターフェースは、たった1つのメソッドを必要とします:

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.Readerio.Writerを受け入れる関数を記述できるようになり、ファイル、ネットワーク接続、バッファ、または任意のカスタム実装で動作させることができます。

challenge icon

チャレンジ

簡単

io.Readerio.Writer インターフェースを実装する、カスタムテキストストリームプロセッサを構築しましょう!データが流れる際にテキストを変換できる型を作成し、これらの基本的なインターフェースがどのように強力な I/O 構成を可能にするかを実証します。

コードは 2 つのファイルに分けて構成します:

  • streams.go: カスタムのリーダー型とライター型を定義します。

    文字列をラップし、読み取られたバイト数を追跡する CountingReader 構造体を作成します。ソース文字列用と位置トラッカー用のフィールドを持たせる必要があります。提供されたスライスにソースからバイトを読み取り、位置を更新し、ソースが使い果たされたときに io.EOF を返す Read(p []byte) (int, error) メソッドを実装してください。

    書き込まれたデータを収集し、大文字に変換する UppercaseWriter 構造体を作成します。累積された結果を内部に保存する必要があります。入力されたバイトを大文字に変換して保存する Write(p []byte) (int, error) メソッドを実装してください。累積された大文字のテキストを取得するための Result() string メソッドを追加します。

    型を適切に初期化するために、コンストラクタ関数 NewCountingReader(source string) *CountingReaderNewUppercaseWriter() *UppercaseWriter を作成します。

  • main.go: 標準ライブラリと連携して動作するカスタム I/O 型を実演します。

    文字列入力を読み取り、CountingReaderio.ReadAll と共に使用してすべてのデータを読み取ります。そのデータを UppercaseWriterWrite メソッドを使用して渡します。

    結果を次の形式で出力してください:

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

以下の入力が提供されます:

  • テキスト 1 行

例えば、次のように与えられた場合:

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

重要なポイントは、CountingReaderio.Reader を実装しているため、io.ReadAll とシームレスに連携することです。Go のエコシステムにおいて io.Reader を受け入れる関数であれば、どのようなものでも作成したカスタム型で動作します。これがインターフェースベースの設計の力です。

チートシート

Goのio.Readerおよびio.Writerインターフェースは、I/O操作の基本です:

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

Readメソッドは、スライスに最大len(p)バイトを読み込み、読み取ったバイト数を返します。Writeメソッドは、スライスからバイトを書き込み、書き込んだバイト数を返します。

これらのメソッドを実装する型は、GoのI/Oエコシステム全体で動作します。カスタムリーダーの例:

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
}

標準ライブラリ関数でカスタムリーダーを使用する:

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

この設計により、io.Readerまたはio.Writerを受け取る関数は、ファイル、ネットワーク接続、バッファ、または任意のカスタム実装で動作できるようになります。

自分で試してみよう

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: 以下の必要なフォーマットで結果を出力する:
	// Original: [input]
	// Uppercase: [result from writer]
	// Bytes read: [total bytes]

	// 未使用のインポートエラーを避けるためのプレースホルダー - 実装時に削除してください
	_ = io.EOF
	fmt.Println("Original:", input)
	// fmt.Println("Uppercase:", upperWriter.Result())
	// fmt.Println("Bytes read:", countingReader.Position)
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン