io.Copy în Go

Copiază toate datele dintr-un Reader într-un Writer, pe bucăți, fără să le țină pe toate în memorie.

Sintaxă
func Copy(dst Writer, src Reader) (written int64, err error)
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)

Ce face io.Copy

io.Copy citește din src și scrie în dst până la io.EOF. Folosește un buffer intern de 32 KB, deci poate copia fișiere de mai mulți GB cu memorie puțină. La final, io.EOF nu este raportat ca eroare.

Exemple tipice: copierea unui fișier, salvarea unui răspuns HTTP pe disc, trimiterea unui fișier către un client, calculul unui hash pentru un fișier mare.

io.CopyBuffer (Go 1.5) face același lucru, dar folosește buffer-ul dat de tine, util când faci multe copieri și vrei să refolosești memoria. Dacă sursa sau destinația au metode speciale (WriterTo, ReaderFrom), ambele funcții le folosesc și buffer-ul poate fi ignorat.

Parametri

Parametru Descriere
dst Destinația (orice io.Writer).
src Sursa (orice io.Reader).
buf La CopyBuffer: buffer-ul de folosit; nu poate avea lungime 0.

Ce returnează

Numărul de octeți copiați și nil, sau prima eroare de citire/scriere.

Exemple

Exemplul 1: Copierea unui fișier

package main

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

func copiaza(sursa, dest string) (int64, error) {
    in, err := os.Open(sursa)
    if err != nil {
        return 0, err
    }
    defer in.Close()

    out, err := os.Create(dest)
    if err != nil {
        return 0, err
    }
    defer out.Close()
    return io.Copy(out, in)
}

func main() {
    os.WriteFile("contract.txt", []byte("Contract nr. 15/2025\nPărțile: ...\n"), 0o644)
    n, err := copiaza("contract.txt", "contract-copie.txt")
    fmt.Println("octeți copiați:", n, "eroare:", err)
    date, _ := os.ReadFile("contract-copie.txt")
    fmt.Print(string(date))
}

Rezultat

octeți copiați: 36 eroare: <nil>
Contract nr. 15/2025
Părțile: ...

Exemplul 2: Hash pentru un fișier mare

Hash-ul este un io.Writer, așa că îi trimitem fișierul cu io.Copy.

package main

import (
    "crypto/sha256"
    "fmt"
    "io"
    "os"
)

func main() {
    os.WriteFile("arhiva.zip", []byte("conținutul arhivei"), 0o644)
    f, _ := os.Open("arhiva.zip")
    defer f.Close()

    h := sha256.New()
    if _, err := io.Copy(h, f); err != nil {
        fmt.Println("eroare:", err)
        return
    }
    fmt.Printf("%x\n", h.Sum(nil))
}

Rezultat

7512493d610addbc8ad628ddd11459710820b4624ee719bc623b82eb30b8b149

Exemplul 3: CopyBuffer cu un buffer refolosit

package main

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

func main() {
    buf := make([]byte, 4)
    surse := []string{"Iași\n", "Cluj-Napoca\n", "Constanța\n"}
    var total int64
    for _, s := range surse {
        // strings.Reader are WriteTo, așa că ascundem metoda ca să se folosească buf
        src := struct{ io.Reader }{strings.NewReader(s)}
        n, _ := io.CopyBuffer(os.Stdout, src, buf)
        total += n
    }
    fmt.Println("total octeți:", total)
}

Rezultat

Iași
Cluj-Napoca
Constanța
total octeți: 29

Greșeli frecvente

  • Citești tot fișierul cu os.ReadFile doar ca să-l scrii în altă parte. io.Copy face asta fără să încarce totul în memorie.
  • Inversezi argumentele: primul este destinația, al doilea sursa (ca la atribuire: dst = src).
  • Verifici err == io.EOF după io.Copy. Funcția returnează nil la sfârșitul normal al datelor.

Comenzi înrudite

  • io.CopyN: Copiază exact n octeți dintr-un Reader într-un Writer (sau mai puțini, cu eroare, dacă sursa se termină).
  • io.Reader: Interfața pentru orice sursă de date din care se poate citi: fișiere, text, conexiuni, răspunsuri HTTP.
  • 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.
  • os.File: Tipul care reprezintă un fișier deschis; îl obții cu os.Open, os.Create sau os.OpenFile.