io.Reader în Go

Interfața pentru orice sursă de date din care se poate citi: fișiere, text, conexiuni, răspunsuri HTTP.

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

Ce face io.Reader

io.Reader este una dintre cele mai importante interfețe din Go. Orice tip care are metoda Read(p []byte) (n int, err error) este un Reader: *os.File, strings.Reader, bytes.Buffer, bufio.Reader, corpul unui răspuns HTTP, o conexiune de rețea.

Regulile metodei: pune până la len(p) octeți în p, returnează câți a pus în n; la sfârșitul datelor returnează io.EOF. Poate returna n > 0 și o eroare în același apel; prelucrează întâi cei n octeți, apoi eroarea.

Avantajul: o funcție care primește un io.Reader merge cu orice sursă. Scrii o dată funcția și o testezi cu strings.NewReader, apoi o folosești cu fișiere sau rețea.

Parametri

Parametru Descriere
p Buffer-ul în care sursa pune datele.

Ce returnează

Numărul de octeți citiți și o eroare (io.EOF la final).

Exemple

Exemplul 1: O funcție, mai multe surse

package main

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

func numaraCuvinte(r io.Reader) int {
    s := bufio.NewScanner(r)
    s.Split(bufio.ScanWords)
    n := 0
    for s.Scan() {
        n++
    }
    return n
}

func main() {
    fmt.Println("text:", numaraCuvinte(strings.NewReader("Ana are mere și pere")))
    fmt.Println("buffer:", numaraCuvinte(bytes.NewBufferString("unu doi trei")))

    os.WriteFile("poveste.txt", []byte("A fost odată ca niciodată"), 0o644)
    f, _ := os.Open("poveste.txt")
    defer f.Close()
    fmt.Println("fișier:", numaraCuvinte(f))
}

Rezultat

text: 5
buffer: 3
fișier: 5

Exemplul 2: Un Reader scris de mână

Tipul nostru produce litera a de n ori. Orice funcție care primește io.Reader îl poate folosi.

package main

import (
    "fmt"
    "io"
)

type repetitor struct {
    ramase int
}

func (r *repetitor) Read(p []byte) (int, error) {
    if r.ramase == 0 {
        return 0, io.EOF
    }
    n := min(len(p), r.ramase)
    for i := range n {
        p[i] = 'a'
    }
    r.ramase -= n
    return n, nil
}

func main() {
    date, _ := io.ReadAll(&repetitor{ramase: 12})
    fmt.Println(string(date), len(date))
}

Rezultat

aaaaaaaaaaaa 12

Exemplul 3: Combinarea Reader-elor

io.MultiReader citește surse una după alta, iar io.LimitReader se oprește după un număr de octeți.

package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    antet := strings.NewReader("Nume,Oraș\n")
    date := strings.NewReader("Ana,Iași\nMihai,Cluj\n")
    tot, _ := io.ReadAll(io.MultiReader(antet, date))
    fmt.Print(string(tot))

    primii, _ := io.ReadAll(io.LimitReader(strings.NewReader("abcdefghij"), 4))
    fmt.Println(string(primii))
}

Rezultat

Nume,Oraș
Ana,Iași
Mihai,Cluj
abcd

Greșeli frecvente

  • Ignori octeții primiți împreună cu o eroare. Un Read poate returna date și io.EOF în același apel.
  • Scrii funcții care primesc *os.File când au nevoie doar să citească. Primește io.Reader: funcția devine mai ușor de testat și de refolosit.

Comenzi înrudite

  • io.Writer: Interfața pentru orice destinație în care se pot scrie octeți: fișiere, ecran, buffer-e, răspunsuri HTTP.
  • io.ReadAll: Citește tot ce oferă un Reader, până la final, și returnează datele ca slice de octeți.
  • io.Copy: Copiază toate datele dintr-un Reader într-un Writer, pe bucăți, fără să le țină pe toate în memorie.
  • os.File.Read: Citește din fișier cel mult len(b) octeți în slice-ul b și returnează câți a citit.
  • bufio.NewReader: Învelește un io.Reader într-un bufio.Reader, care citește pe blocuri și oferă ReadString, ReadRune, ReadLine.
  • io.ReadWriter: Interfața pentru tipurile din care se poate și citi, și în care se poate scrie (Reader + Writer).
  • io.MultiReader: Leagă mai multe Reader-e unul după altul, ca și cum ar fi o singură sursă continuă.
  • io.LimitReader: Învelește un Reader astfel încât să poată fi citiți din el cel mult n octeți; după aceea dă io.EOF.