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)) // AAAAADieses 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.
Aufgabe
EinfachLassen 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 MethodeRead(p []byte) (int, error), die Bytes aus der Quelle in den bereitgestellten Slice liest, die Position aktualisiert undio.EOFzurü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 MethodeWrite(p []byte) (int, error), die eingehende Bytes in Großbuchstaben umwandelt und speichert. Fügen Sie eine MethodeResult() stringhinzu, um den akkumulierten Text in Großbuchstaben abzurufen.Erstellen Sie Konstruktor-Funktionen
NewCountingReader(source string) *CountingReaderundNewUppercaseWriter() *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
CountingReadermitio.ReadAll, um alle Daten zu lesen. Leiten Sie diese Daten über die MethodeWritean IhrenUppercaseWriterweiter.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 WorldSollte Ihre Ausgabe wie folgt aussehen:
Original: Hello World
Uppercase: HELLO WORLD
Bytes read: 11Und bei der Eingabe:
Go interfaces are powerfulSollte Ihre Ausgabe wie folgt aussehen:
Original: Go interfaces are powerful
Uppercase: GO INTERFACES ARE POWERFUL
Bytes read: 26Die 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)) // AAAAADieses 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)
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser11Standardbibliothek & OOP
io.Reader & io.Writersort.Interfacefmt.Stringer Interfaceencoding/json mit Structshttp.Handler InterfaceZusammenfassung - REST-API-Modelle3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool