Featured image of post Confronto tra ReadRune, ReadBytes, ReadString e ReadLine in Go

Confronto tra ReadRune, ReadBytes, ReadString e ReadLine in Go

Tabella comparativa e diagramma di flusso per scegliere il metodo corretto di bufio.Reader in Go, con esempio pratico

πŸ“š Confronto metodi di bufio.Reader in Go

Introduzione

Recentemente sto studiando Go, linguaggio interessante e per me Γ¨ un ritorno (ho iniziato a programmare col C) ad un tipo di programmazione profondamente diversa da quella che ho praticato negli ultimi anni specializzandomi su PHP/Laravel. Una delle cose che mi interessano Γ¨ come leggere e scrivere da uno stream quindi, con l’aiuto di una IA mi sono fatto un veloce riepilogo delle differenze tra alcuni metodi della libreria bufio.

I metodi ReadRune, ReadBytes, ReadString e ReadLine sono molto simili ma solo in apparenza. Ecco una guida rapida per capire le differenze.


Tabella comparativa

MetodoFirmaCosa leggeTipo restituitoInclude \n?Note pratiche
ReadRune()(r rune, size int, err error)Un singolo carattere Unicode (1 rune)rune (+size+err)N/A (legge solo 1 carattere)Utile per analizzare caratteri uno alla volta (UTF-8 safe).
ReadBytes(delim byte)([]byte, error)Fino a delim incluso[]byteβœ… sΓ¬Restituisce slice di byte, ottimo per parsing binario o testo grezzo.
ReadString(delim byte)(string, error)Fino a delim inclusostringβœ… sΓ¬Comodo se sai di leggere testo, meno efficiente di ReadBytes.
ReadLine()([]byte, isPrefix bool, error)Una riga (senza \n)[]byte + isPrefix + err❌ noSe la riga è troppo lunga, la restituisce a pezzi (isPrefix = true). Serve loop per ricomporla. Più basso livello.

πŸ”€ Diagramma di flusso decisionale

                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                     β”‚ Vuoi leggere 1 carattere?    β”‚
                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                     β”‚
                           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                           β”‚ Usa ReadRune()    β”‚
                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

                                     β”‚
                                     β–Ό
                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                     β”‚ Vuoi leggere fino a un       β”‚
                     β”‚ delimitatore (es. '\n')?     β”‚
                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                     β”‚
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                   β”‚                 |                 β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      |      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ Ti serve un []byte? β”‚      |      β”‚ Ti serve una stringa?β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      |      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚                 |                  β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      |      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ Usa ReadBytes()     β”‚      |      β”‚ Usa ReadString()      β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      |      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                     β”‚
                                     β”‚
                                     β–Ό
                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                     β”‚ Devi gestire linee molto     β”‚
                     β”‚ lunghe (buffer a pezzi)?     β”‚
                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                     β”‚
                           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                           β”‚ Usa ReadLine()    β”‚
                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Esempio pratico

package main

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

func main() {
	text := "cafΓ©\nseconda linea\nterza linea senza newline finale"

	// --- ReadRune(): legge un singolo carattere Unicode alla volta ---
	fmt.Println("== ReadRune() ==")
	r := bufio.NewReader(strings.NewReader(text))
	for i := 0; i < 4; i++ { // leggo le prime 4 rune: c, a, f, Γ©
		ru, size, err := r.ReadRune()
		if err != nil {
			fmt.Println("Errore ReadRune:", err)
			break
		}
		fmt.Printf("rune=%q size=%d\n", ru, size)
	}
	fmt.Println()

	// --- ReadBytes('\n'): legge fino al delimitatore incluso e restituisce []byte ---
	fmt.Println("== ReadBytes('\\n') ==")
	r = bufio.NewReader(strings.NewReader(text))
	b, err := r.ReadBytes('\n')
	if err != nil {
		fmt.Println("Errore ReadBytes:", err)
	}
	fmt.Printf("ReadBytes: %q (len=%d)\n", b, len(b))
	fmt.Println()

	// --- ReadString('\n'): stesso comportamento ma restituisce string ---
	fmt.Println("== ReadString('\\n') ==")
	r = bufio.NewReader(strings.NewReader(text))
	s, err := r.ReadString('\n')
	if err != nil {
		fmt.Println("Errore ReadString:", err)
	}
	fmt.Printf("ReadString: %q (len=%d)\n", s, len(s))
	fmt.Println()

	// --- ReadLine(): NON include '\n' e puΓ² restituire porzioni (isPrefix=true) ---
	// Forziamo una linea piΓΉ lunga del buffer per vedere isPrefix=true.
	fmt.Println("== ReadLine() con buffer piccolo e isPrefix ==")
	longLine := "abcdefghij\n" // 10 caratteri + '\n'
	smallBuf := bufio.NewReaderSize(strings.NewReader(longLine), 8)

	var parts [][]byte
	for {
		line, isPrefix, err := smallBuf.ReadLine()
		if err != nil {
			// fine input o errore
			break
		}
		// Copia difensiva del frammento letto
		parts = append(parts, append([]byte(nil), line...))
		fmt.Printf("chunk=%q isPrefix=%v\n", line, isPrefix)
		if !isPrefix {
			break
		}
	}
	recombined := bytes.Join(parts, nil)
	fmt.Printf("Ricomposta: %q (pezzi=%d)\n", recombined, len(parts))
	fmt.Println()

	// --- Esempio classico di ReadLine su testo normale (senza superare il buffer) ---
	fmt.Println("== ReadLine() normale ==")
	r = bufio.NewReader(strings.NewReader(text))
	line, isPrefix, err := r.ReadLine()
	if err == nil {
		fmt.Printf("line=%q isPrefix=%v (niente '\\n')\n", line, isPrefix)
	}
}

Conclusioni

In sintesi veloce:

  • ReadRune β†’ analisi carattere-per-carattere, sicuro su UTF-8.
  • ReadBytes β†’ legge fino al delimitatore e restituisce []byte (ottimo per parsing binario/grezzo).
  • ReadString β†’ come sopra ma restituisce string (piΓΉ comodo, piΓΉ allocazioni).
  • ReadLine β†’ non include \n, gestisce linee molto lunghe a pezzi con isPrefix.

Insidie comuni da ricordare:

  • Fine riga su Windows Γ¨ \r\n: rimuovi il \r finale se leggi fino a \n.
  • bufio.Scanner ha un limite di token (β‰ˆ64 KB) che va aumentato per righe grandi.
  • Le allocazioni: ReadString crea sempre una nuova stringa; con ReadBytes puoi restare su []byte finchΓ© serve.

Cosa usare quando:

  • Parsing binario o attenzione alle allocazioni β†’ ReadBytes.
  • Input testuale β€œline-oriented” β†’ ReadString (oppure Scanner).
  • Analisi fine di rune (accenti/emoji) β†’ ReadRune.
  • Log/file con righe enormi β†’ ReadLine (ricomponi quando isPrefix == true).
Federico Maiorini
Realizzato con Hugo
Tema Stack realizzato da Jimmy