Menu
Coddy logo textTech

io.Reader & io.Writer

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 73 von 107.

Die Standardbibliothek von Go demonstriert interface-basiertes Design durch zwei grundlegende Schnittstellen: io.Reader und io.Writer. Diese einfachen Schnittstellen treiben alles an, von Dateioperationen bis hin zur Netzwerkkommunikation.

Die io.Reader-Schnittstelle erfordert nur eine Methode:

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

Es liest bis zu len(p) Bytes in den Slice ein und gibt zurück, wie viele Bytes gelesen wurden. Analog dazu definiert io.Writer:

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

Jeder Typ, der diese Methoden implementiert, funktioniert mit dem gesamten I/O-Ökosystem.

Hier ist ein benutzerdefinierter Typ, der io.Reader implementiert:

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
}

Jetzt funktioniert dieser benutzerdefinierte Reader mit jeder Funktion, die einen io.Reader erwartet:

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

Dieses Design ermöglicht es Ihnen, Funktionen zu schreiben, die io.Reader oder io.Writer akzeptieren, wodurch sie mit Dateien, Netzwerkverbindungen, Buffern oder jeder benutzerdefinierten Implementierung funktionieren.

challenge icon

Aufgabe

Einfach

Lassen Sie uns einen benutzerdefinierten Text-Stream-Prozessor erstellen, der die Schnittstellen io.Reader und io.Writer implementiert! Sie werden Typen erstellen, die Text transformieren können, während er durch sie fließt, und so demonstrieren, wie diese grundlegenden Schnittstellen eine leistungsstarke I/O-Komposition ermöglichen.

Sie werden Ihren Code in zwei Dateien organisieren:

  • streams.go: Definieren Sie Ihre benutzerdefinierten Reader- und Writer-Typen.

    Erstellen Sie ein CountingReader-Struct, das einen String umschließt und verfolgt, wie viele Bytes gelesen wurden. Es sollte Felder für den Quell-String und einen Positions-Tracker haben. Implementieren Sie die Methode Read(p []byte) (int, error), die Bytes aus der Quelle in den bereitgestellten Slice liest, die Position aktualisiert und io.EOF zurückgibt, wenn die Quelle erschöpft ist.

    Erstellen Sie ein UppercaseWriter-Struct, das geschriebene Daten sammelt und in Großbuchstaben umwandelt. Es sollte das akkumulierte Ergebnis intern speichern. Implementieren Sie die Methode Write(p []byte) (int, error), die eingehende Bytes in Großbuchstaben umwandelt und speichert. Fügen Sie eine Methode Result() string hinzu, um den akkumulierten Text in Großbuchstaben abzurufen.

    Erstellen Sie Konstruktor-Funktionen NewCountingReader(source string) *CountingReader und NewUppercaseWriter() *UppercaseWriter, um Ihre Typen ordnungsgemäß zu initialisieren.

  • main.go: Demonstrieren Sie Ihre benutzerdefinierten I/O-Typen im Zusammenspiel mit der Standardbibliothek.

    Lesen Sie eine String-Eingabe ein und verwenden Sie dann Ihren CountingReader mit io.ReadAll, um alle Daten zu lesen. Leiten Sie diese Daten über die Methode Write an Ihren UppercaseWriter weiter.

    Geben Sie die Ergebnisse in diesem Format aus:

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

Die folgende Eingabe wird bereitgestellt:

  • Eine einzelne Textzeile

Zum Beispiel bei der Eingabe:

Hello World

Sollte Ihre Ausgabe wie folgt aussehen:

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

Und bei der Eingabe:

Go interfaces are powerful

Sollte Ihre Ausgabe wie folgt aussehen:

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

Die entscheidende Erkenntnis ist, dass Ihr CountingReader nahtlos mit io.ReadAll zusammenarbeitet, da er io.Reader implementiert. Jede Funktion im Go-Ökosystem, die einen io.Reader akzeptiert, wird mit Ihrem benutzerdefinierten Typ funktionieren – das ist die Stärke des schnittstellenbasierten Designs.

Spickzettel

Gos io.Reader- und io.Writer-Interfaces sind grundlegend für I/O-Operationen:

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

Die Read-Methode liest bis zu len(p) Bytes in den Slice und gibt die Anzahl der gelesenen Bytes zurück. Die Write-Methode schreibt Bytes aus dem Slice und gibt die Anzahl der geschriebenen Bytes zurück.

Jeder Typ, der diese Methoden implementiert, funktioniert mit dem gesamten I/O-Ökosystem von Go. Beispiel für einen benutzerdefinierten 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
}

Verwendung des benutzerdefinierten Readers mit Funktionen der Standardbibliothek:

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

Dieses Design ermöglicht es Funktionen, die io.Reader oder io.Writer akzeptieren, mit Dateien, Netzwerkverbindungen, Buffern oder jeder benutzerdefinierten Implementierung zu arbeiten.

Probier es selbst

package main

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

func main() {
	// Eingabe lesen
	reader := bufio.NewReader(os.Stdin)
	input, _ := reader.ReadString('\n')
	// Abschließenden Zeilenumbruch entfernen, falls vorhanden
	if len(input) > 0 && input[len(input)-1] == '\n' {
		input = input[:len(input)-1]
	}

	// TODO: Erstelle einen CountingReader mit dem Eingabestring
	// countingReader := NewCountingReader(input)

	// TODO: Verwende io.ReadAll, um alle Daten aus deinem CountingReader zu lesen
	// data, err := io.ReadAll(countingReader)

	// TODO: Erstelle einen UppercaseWriter
	// upperWriter := NewUppercaseWriter()

	// TODO: Schreibe die Daten in deinen UppercaseWriter
	// upperWriter.Write(data)

	// TODO: Gib die Ergebnisse im erforderlichen Format aus:
	// Original: [input]
	// Uppercase: [result from writer]
	// Bytes read: [total bytes]

	// Platzhalter, um Fehler wegen nicht verwendeter Imports zu vermeiden - beim Implementieren entfernen
	_ = io.EOF
	fmt.Println("Original:", input)
	// fmt.Println("Uppercase:", upperWriter.Result())
	// fmt.Println("Bytes read:", countingReader.Position)
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung