Bufio #

Setiap kali kamu membaca dari file atau koneksi jaringan satu byte atau satu baris pada satu waktu, Go melakukan system call ke kernel untuk setiap operasi baca — dan system call itu mahal. Package bufio menyelesaikan masalah ini dengan menambahkan lapisan buffer di antara kode Go dan sumber I/O: ia membaca data dalam blok besar sekaligus (biasanya 4096 byte) dan menyimpannya di memori, sehingga operasi baca kecil-kecil berikutnya dilayani dari buffer tanpa system call tambahan. Hasilnya bisa 10–100× lebih cepat untuk I/O yang melibatkan banyak operasi kecil. Package ini menyediakan tiga tipe utama: Scanner untuk membaca per baris atau per token, Reader untuk buffered read dengan kemampuan peek dan unread, dan Writer untuk buffered write yang menyatukan banyak operasi tulis kecil menjadi satu operasi besar.

Gambaran Besar Package bufio #

flowchart TD
    IO["I/O Source\n(file, net.Conn, os.Stdin, dll)"] --> BufIO["package bufio"]

    BufIO --> Scanner["bufio.Scanner\nMembaca per token/baris\nAPI paling mudah"]
    BufIO --> Reader["bufio.Reader\nBuffered read dengan\nPeek, ReadString, ReadLine"]
    BufIO --> Writer["bufio.Writer\nBuffered write\nFlush wajib dipanggil"]

    Scanner --> ScanLines["ScanLines — default\nbaca per baris"]
    Scanner --> ScanWords["ScanWords\nbaca per kata"]
    Scanner --> ScanBytes["ScanBytes\nbaca per byte"]
    Scanner --> ScanRunes["ScanRunes\nbaca per rune"]
    Scanner --> Custom["SplitFunc kustom\ntokenisasi apapun"]

    Reader --> RS["ReadString(delim)\nbaca sampai delimiter"]
    Reader --> RL["ReadLine()\nbaca satu baris (low-level)"]
    Reader --> RB["ReadByte / ReadRune\nbaca satu unit"]
    Reader --> Peek["Peek(n)\nlihat tanpa konsumsi"]

    Writer --> WS["WriteString\nWrite / WriteByte\nWriteRune"]
    Writer --> Flush["Flush()\nkosongkan buffer ke tujuan"]

    style IO fill:#4f86c6,color:#fff
    style BufIO fill:#e8f5e9
    style Scanner fill:#e3f2fd
    style Reader fill:#fff3e0
    style Writer fill:#f3e5f5

bufio.Scanner — Membaca Per Token #

bufio.Scanner adalah cara paling mudah untuk membaca input baris per baris atau token per token. Ia menyembunyikan kompleksitas buffering dan memberikan API yang bersih:

package main

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

func main() {
    // Membaca dari string (untuk ilustrasi)
    input := "baris pertama\nbaris kedua\nbaris ketiga\n"
    scanner := bufio.NewScanner(strings.NewReader(input))

    // Scan — kembalikan true jika ada token berikutnya
    for scanner.Scan() {
        fmt.Println(scanner.Text()) // teks baris tanpa newline
        // atau scanner.Bytes() untuk []byte tanpa alokasi
    }

    // WAJIB: periksa error setelah loop selesai
    // scanner.Err() mengembalikan nil jika loop selesai karena EOF
    if err := scanner.Err(); err != nil {
        fmt.Fprintf(os.Stderr, "error membaca: %v\n", err)
    }
}

Membaca dari Berbagai Sumber #

// Dari file
func bacaFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("bacaFile: %w", err)
    }
    defer f.Close()

    scanner := bufio.NewScanner(f)
    nomorBaris := 0

    for scanner.Scan() {
        nomorBaris++
        baris := scanner.Text()

        // Skip baris kosong dan komentar
        if baris == "" || strings.HasPrefix(baris, "#") {
            continue
        }

        fmt.Printf("%4d: %s\n", nomorBaris, baris)
    }

    return scanner.Err()
}

// Dari stdin — untuk CLI tools
func bacaStdin() {
    scanner := bufio.NewScanner(os.Stdin)
    fmt.Print("Masukkan teks (Ctrl+D untuk selesai):\n")

    for scanner.Scan() {
        teks := scanner.Text()
        // Proses setiap baris yang dimasukkan
        fmt.Printf("Kamu menulis: %s\n", strings.ToUpper(teks))
    }
}

// Dari HTTP response body
func bacaResponse(resp *http.Response) error {
    defer resp.Body.Close()
    scanner := bufio.NewScanner(resp.Body)

    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    return scanner.Err()
}

Split Functions — Mengubah Cara Tokenisasi #

Scanner menggunakan SplitFunc untuk menentukan bagaimana memisahkan input menjadi token. Default adalah ScanLines:

flowchart LR
    Input["Input: 'Hello, World! How are you?'"] --> SF["SplitFunc"]

    SF --> SL["ScanLines\n→ token per baris"]
    SF --> SW["ScanWords\n→ token per kata"]
    SF --> SB["ScanBytes\n→ token per byte"]
    SF --> SR["ScanRunes\n→ token per rune"]
    SF --> SC["Custom SplitFunc\n→ token apapun"]

    SL --> OL["'Hello, World! How are you?'"]
    SW --> OW["'Hello,' 'World!' 'How' 'are' 'you?'"]
    SB --> OB["'H' 'e' 'l' 'l' 'o' ..."]
    SC --> OC["Apapun yang kamu definisikan"]

    style SC fill:#e8f5e9
    style OC fill:#e8f5e9
// ScanWords — baca per kata
scanner := bufio.NewScanner(strings.NewReader("satu dua   tiga\nempat"))
scanner.Split(bufio.ScanWords)

for scanner.Scan() {
    fmt.Printf("[%s]\n", scanner.Text())
}
// [satu]
// [dua]
// [tiga]
// [empat]

// ScanBytes — baca per byte
scanner2 := bufio.NewScanner(strings.NewReader("ABC"))
scanner2.Split(bufio.ScanBytes)

for scanner2.Scan() {
    fmt.Printf("0x%02X\n", scanner2.Bytes()[0])
}
// 0x41
// 0x42
// 0x43

// ScanRunes — baca per rune (mendukung multibyte Unicode)
scanner3 := bufio.NewScanner(strings.NewReader("Héllo"))
scanner3.Split(bufio.ScanRunes)

for scanner3.Scan() {
    fmt.Printf("[%s]\n", scanner3.Text())
}
// [H]
// [é]
// [l]
// [l]
// [o]

Custom SplitFunc #

// SplitFunc signature:
// func(data []byte, atEOF bool) (advance int, token []byte, err error)

// Contoh: tokenisasi CSV sederhana (pisahkan per koma)
func scanCSV(data []byte, atEOF bool) (int, []byte, error) {
    // Jika tidak ada data dan sudah EOF, selesai
    if atEOF && len(data) == 0 {
        return 0, nil, nil
    }

    // Cari koma
    if i := bytes.IndexByte(data, ','); i >= 0 {
        // Ada koma — kembalikan token sebelumnya
        return i + 1, bytes.TrimSpace(data[:i]), nil
    }

    // Tidak ada koma
    if atEOF {
        // Ini token terakhir
        return len(data), bytes.TrimSpace(data), nil
    }

    // Minta data lebih banyak
    return 0, nil, nil
}

// Contoh: baca per paragraf (pisahkan di baris kosong)
func scanParagraph(data []byte, atEOF bool) (int, []byte, error) {
    if atEOF && len(data) == 0 {
        return 0, nil, nil
    }

    // Cari dua newline berturut-turut (baris kosong)
    if i := bytes.Index(data, []byte("\n\n")); i >= 0 {
        return i + 2, bytes.TrimSpace(data[:i]), nil
    }

    if atEOF {
        return len(data), bytes.TrimSpace(data), nil
    }

    return 0, nil, nil
}

// Penggunaan custom split
csvData := "apel, mangga, jeruk, pisang, durian"
scanner := bufio.NewScanner(strings.NewReader(csvData))
scanner.Split(scanCSV)

var buah []string
for scanner.Scan() {
    buah = append(buah, scanner.Text())
}
fmt.Println(buah) // [apel mangga jeruk pisang durian]

Menangani Buffer yang Besar #

Scanner memiliki buffer default 64KB. Jika baris lebih panjang dari itu, scanner akan error:

// ANTI-PATTERN: scanner akan error untuk baris > 64KB
scanner := bufio.NewScanner(hugeFile)
for scanner.Scan() { ... } // bisa gagal dengan "token too long"

// BENAR: perbesar buffer untuk file dengan baris panjang
const maxBufSize = 10 * 1024 * 1024 // 10 MB
scanner := bufio.NewScanner(hugeFile)
scanner.Buffer(make([]byte, maxBufSize), maxBufSize)

for scanner.Scan() {
    // sekarang bisa handle baris sampai 10 MB
}

bufio.Reader — Buffered Read dengan Lookahead #

bufio.Reader memberikan kontrol lebih detail dibanding Scanner — berguna untuk parsing format yang kompleks di mana kamu perlu membaca karakter satu per satu, melihat ke depan, atau membaca dengan delimiter kustom:

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

reader := bufio.NewReader(strings.NewReader("Halo, Dunia!\nBaris kedua\n"))

// ReadString — baca sampai delimiter (delimiter ikut terbawa)
baris, err := reader.ReadString('\n')
fmt.Printf("[%s] err=%v\n", baris, err)
// [Halo, Dunia!\n] err=<nil>

baris, err = reader.ReadString('\n')
fmt.Printf("[%s] err=%v\n", baris, err)
// [Baris kedua\n] err=<nil>

baris, err = reader.ReadString('\n')
fmt.Printf("[%s] err=%v\n", baris, err)
// [] err=EOF

// ReadByte — baca satu byte
reader2 := bufio.NewReader(strings.NewReader("ABC"))
b, _ := reader2.ReadByte()
fmt.Printf("%c\n", b) // A

// UnreadByte — kembalikan byte terakhir ke buffer
reader2.UnreadByte()
b, _ = reader2.ReadByte()
fmt.Printf("%c\n", b) // A lagi!

// ReadRune — baca satu rune (mendukung multibyte)
reader3 := bufio.NewReader(strings.NewReader("Héllo"))
r, size, err := reader3.ReadRune()
fmt.Printf("%c (size=%d)\n", r, size) // H (size=1)

r, size, err = reader3.ReadRune()
fmt.Printf("%c (size=%d)\n", r, size) // é (size=2) — dua byte!

// Peek — lihat N byte ke depan tanpa mengonsumsinya
reader4 := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK"))
peeked, _ := reader4.Peek(4)
fmt.Println(string(peeked)) // HTTP — posisi baca tidak bergerak

next, _ := reader4.Peek(4)
fmt.Println(string(next)) // HTTP — masih sama, belum dikonsumsi

b2, _ := reader4.ReadByte()
fmt.Printf("%c\n", b2) // H — sekarang baru dikonsumsi

ReadLine vs ReadString #

// ReadLine — low-level, tidak menyertakan newline, bisa return partial line
line, isPrefix, err := reader.ReadLine()
// isPrefix=true jika baris terlalu panjang dan harus dibaca lagi
// line tidak menyertakan \n atau \r\n

// ReadString — lebih mudah, menyertakan delimiter, menangani baris panjang
baris, err := reader.ReadString('\n')
// baris menyertakan \n di akhir
// untuk mendapatkan teks tanpa \n: strings.TrimRight(baris, "\r\n")

// Untuk kebanyakan kasus, ReadString lebih direkomendasikan dari ReadLine

Penggunaan Peek untuk Deteksi Format #

// Deteksi format data sebelum parsing
func deteksiDanParse(r io.Reader) error {
    br := bufio.NewReader(r)

    // Lihat 4 byte pertama tanpa mengonsumsinya
    header, err := br.Peek(4)
    if err != nil {
        return fmt.Errorf("deteksiDanParse peek: %w", err)
    }

    switch {
    case bytes.HasPrefix(header, []byte("{")):
        // JSON object
        return parseJSON(br)
    case bytes.HasPrefix(header, []byte("[")):
        // JSON array
        return parseJSON(br)
    case bytes.HasPrefix(header, []byte("<?xm")):
        // XML
        return parseXML(br)
    case bytes.HasPrefix(header, []byte("\x1f\x8b")):
        // gzip magic bytes
        return parseGzip(br)
    default:
        // Asumsikan teks biasa
        return parseTeks(br)
    }
}

bufio.Writer — Buffered Write #

bufio.Writer mengumpulkan data kecil-kecil dalam buffer dan menulisnya ke tujuan dalam blok besar, mengurangi jumlah system call secara dramatis:

sequenceDiagram
    participant App as Aplikasi
    participant BW as bufio.Writer\n(buffer 4096 byte)
    participant File as File/Network

    App->>BW: WriteString("baris 1\n") — 8 byte
    Note over BW: buffer: [baris 1\n] (8/4096)
    App->>BW: WriteString("baris 2\n") — 8 byte
    Note over BW: buffer: [baris 1\nbaris 2\n] (16/4096)
    App->>BW: ... (tulis 500 baris kecil)
    Note over BW: buffer penuh (4096 byte)
    BW->>File: Satu write system call — 4096 byte
    App->>BW: Flush()
    BW->>File: Satu write system call — sisa buffer

    Note over App,File: Tanpa bufio: 500 system calls\nDengan bufio: 2-3 system calls
import (
    "bufio"
    "fmt"
    "os"
)

func tulisLog(path string, entri []string) error {
    f, err := os.Create(path)
    if err != nil {
        return fmt.Errorf("tulisLog: %w", err)
    }
    defer f.Close()

    // Bungkus file dengan bufio.Writer
    w := bufio.NewWriter(f)

    for _, e := range entri {
        // Operasi tulis kecil dikumpulkan di buffer
        fmt.Fprintln(w, e)
    }

    // WAJIB: Flush memastikan semua data di buffer ditulis ke file
    // defer f.Close() TIDAK otomatis flush bufio.Writer!
    if err := w.Flush(); err != nil {
        return fmt.Errorf("tulisLog flush: %w", err)
    }

    return nil
}

// Kontrol ukuran buffer
w := bufio.NewWriterSize(f, 64*1024) // 64 KB buffer

// Periksa berapa banyak yang belum di-flush
fmt.Println(w.Buffered()) // jumlah byte di buffer
fmt.Println(w.Available()) // ruang kosong di buffer

// Tulis berbagai tipe
w.WriteString("teks biasa\n")
w.WriteByte('\n')
w.WriteRune('é')
w.Write([]byte{0x00, 0x01, 0x02})
fmt.Fprintf(w, "format %d %s\n", 42, "halo")
Selalu panggil w.Flush() sebelum file ditutup. defer f.Close() tidak akan men-flush bufio.Writer secara otomatis. Data yang masih di buffer saat file ditutup akan hilang tanpa error apapun — ini adalah bug yang sulit dideteksi karena program tidak melaporkan kesalahan.

Pola Aman dengan Flush di defer #

func tulisFileAman(path string, fn func(*bufio.Writer) error) error {
    f, err := os.Create(path)
    if err != nil {
        return err
    }
    defer f.Close()

    w := bufio.NewWriter(f)

    if err := fn(w); err != nil {
        return err
    }

    // Flush eksplisit — jangan andalkan defer f.Close()
    return w.Flush()
}

// Penggunaan
err := tulisFileAman("output.txt", func(w *bufio.Writer) error {
    for i := 0; i < 1000; i++ {
        fmt.Fprintf(w, "baris %d\n", i)
    }
    return nil
})

Pola Penggunaan di Produksi #

Parser Konfigurasi Format Key=Value #

type Config map[string]string

// Format: key=value, satu per baris, # untuk komentar
func parseConfig(r io.Reader) (Config, error) {
    cfg := make(Config)
    scanner := bufio.NewScanner(r)
    nomorBaris := 0

    for scanner.Scan() {
        nomorBaris++
        baris := strings.TrimSpace(scanner.Text())

        // Skip baris kosong dan komentar
        if baris == "" || strings.HasPrefix(baris, "#") {
            continue
        }

        // Pisahkan key=value
        idx := strings.IndexByte(baris, '=')
        if idx < 0 {
            return nil, fmt.Errorf("baris %d: format tidak valid, harus key=value: %q",
                nomorBaris, baris)
        }

        key := strings.TrimSpace(baris[:idx])
        val := strings.TrimSpace(baris[idx+1:])

        if key == "" {
            return nil, fmt.Errorf("baris %d: key tidak boleh kosong", nomorBaris)
        }

        cfg[key] = val
    }

    if err := scanner.Err(); err != nil {
        return nil, fmt.Errorf("parseConfig: %w", err)
    }

    return cfg, nil
}

// Penggunaan
configText := `
# Konfigurasi aplikasi
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp

# Pengaturan server
SERVER_PORT=8080
DEBUG=true
`

cfg, err := parseConfig(strings.NewReader(configText))
if err == nil {
    fmt.Println(cfg["DB_HOST"])    // localhost
    fmt.Println(cfg["SERVER_PORT"]) // 8080
}

Membaca dan Menulis CSV Baris per Baris #

// Proses CSV besar baris per baris tanpa load ke memori
func prosesCSVBesar(input io.Reader, output io.Writer) error {
    scanner := bufio.NewScanner(input)
    writer := bufio.NewWriter(output)
    defer writer.Flush()

    // Baca header
    if !scanner.Scan() {
        if err := scanner.Err(); err != nil {
            return fmt.Errorf("baca header: %w", err)
        }
        return fmt.Errorf("file CSV kosong")
    }
    header := scanner.Text()

    // Tulis header ke output
    fmt.Fprintln(writer, header)

    // Proses setiap baris data
    jumlah := 0
    for scanner.Scan() {
        baris := scanner.Text()
        if baris == "" {
            continue
        }

        // Transformasi baris (contoh: ubah kolom tertentu)
        diproses := transformasiBaris(baris)
        fmt.Fprintln(writer, diproses)
        jumlah++

        // Flush setiap 1000 baris untuk progress yang terlihat
        if jumlah%1000 == 0 {
            if err := writer.Flush(); err != nil {
                return fmt.Errorf("flush setelah %d baris: %w", jumlah, err)
            }
            fmt.Fprintf(os.Stderr, "Diproses: %d baris\r", jumlah)
        }
    }

    fmt.Fprintf(os.Stderr, "\nSelesai: %d baris\n", jumlah)
    return scanner.Err()
}

Parser Protokol HTTP Sederhana #

// Parse HTTP request line dan headers dari koneksi TCP
func parseHTTPRequest(conn net.Conn) (*HTTPRequest, error) {
    reader := bufio.NewReader(conn)

    // Baca request line: "GET /path HTTP/1.1"
    requestLine, err := reader.ReadString('\n')
    if err != nil {
        return nil, fmt.Errorf("baca request line: %w", err)
    }
    requestLine = strings.TrimRight(requestLine, "\r\n")

    bagian := strings.SplitN(requestLine, " ", 3)
    if len(bagian) != 3 {
        return nil, fmt.Errorf("request line tidak valid: %q", requestLine)
    }

    req := &HTTPRequest{
        Method:  bagian[0],
        Path:    bagian[1],
        Version: bagian[2],
        Headers: make(map[string]string),
    }

    // Baca headers sampai baris kosong
    for {
        baris, err := reader.ReadString('\n')
        if err != nil {
            return nil, fmt.Errorf("baca header: %w", err)
        }
        baris = strings.TrimRight(baris, "\r\n")

        if baris == "" {
            break // baris kosong = akhir headers
        }

        idx := strings.IndexByte(baris, ':')
        if idx < 0 {
            continue
        }

        key := strings.TrimSpace(baris[:idx])
        val := strings.TrimSpace(baris[idx+1:])
        req.Headers[key] = val
    }

    // Body bisa dibaca dari reader berikutnya jika Content-Length ada
    if cl := req.Headers["Content-Length"]; cl != "" {
        length, _ := strconv.Atoi(cl)
        req.Body = make([]byte, length)
        io.ReadFull(reader, req.Body)
    }

    return req, nil
}

Word Count — Hitung Kata dari File Besar #

func hitungKata(path string) (map[string]int, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, fmt.Errorf("hitungKata: %w", err)
    }
    defer f.Close()

    hitungan := make(map[string]int)
    scanner := bufio.NewScanner(f)
    scanner.Split(bufio.ScanWords)

    for scanner.Scan() {
        kata := strings.ToLower(scanner.Text())
        // Hapus tanda baca
        kata = strings.Trim(kata, ".,!?;:\"'()[]{}")
        if kata != "" {
            hitungan[kata]++
        }
    }

    return hitungan, scanner.Err()
}

Server TCP dengan bufio #

// Handler koneksi TCP dengan bufio untuk baca/tulis yang efisien
func handleConn(conn net.Conn) {
    defer conn.Close()

    reader := bufio.NewReader(conn)
    writer := bufio.NewWriter(conn)

    for {
        // Baca satu baris dari client
        baris, err := reader.ReadString('\n')
        if err != nil {
            if err != io.EOF {
                fmt.Fprintf(os.Stderr, "baca error: %v\n", err)
            }
            return
        }

        perintah := strings.TrimRight(baris, "\r\n")
        fmt.Printf("Diterima: %q\n", perintah)

        // Proses perintah
        var respons string
        switch strings.ToUpper(perintah) {
        case "PING":
            respons = "PONG"
        case "TIME":
            respons = time.Now().Format(time.RFC3339)
        case "QUIT":
            writer.WriteString("BYE\n")
            writer.Flush()
            return
        default:
            respons = "ERROR: perintah tidak dikenal"
        }

        // Kirim respons
        writer.WriteString(respons + "\n")
        if err := writer.Flush(); err != nil {
            fmt.Fprintf(os.Stderr, "tulis error: %v\n", err)
            return
        }
    }
}

Performa: Dengan dan Tanpa bufio #

flowchart LR
    subgraph Tanpa["Tanpa bufio — 10.000 baris"]
        T1["Write line 1\n→ syscall"] 
        T2["Write line 2\n→ syscall"]
        T3["Write line 3\n→ syscall"]
        T4["... 9.997 syscall lagi"]
        T1 --> T2 --> T3 --> T4
    end

    subgraph Dengan["Dengan bufio — 10.000 baris"]
        D1["Write 512 baris ke buffer"]
        D2["1 syscall ke disk"]
        D3["Write 512 baris berikutnya"]
        D4["1 syscall ke disk"]
        D5["... ~20 syscall total"]
        D1 --> D2 --> D3 --> D4 --> D5
    end

    style Tanpa fill:#fce4ec
    style Dengan fill:#e8f5e9
import (
    "bufio"
    "os"
    "testing"
)

// Benchmark: menulis 10.000 baris ke file
func BenchmarkTanpaBufio(b *testing.B) {
    f, _ := os.Create("/tmp/test.txt")
    defer f.Close()

    for i := 0; i < b.N; i++ {
        for j := 0; j < 10000; j++ {
            fmt.Fprintf(f, "baris %d\n", j)
        }
    }
}

func BenchmarkDenganBufio(b *testing.B) {
    f, _ := os.Create("/tmp/test.txt")
    defer f.Close()
    w := bufio.NewWriter(f)

    for i := 0; i < b.N; i++ {
        for j := 0; j < 10000; j++ {
            fmt.Fprintf(w, "baris %d\n", j)
        }
        w.Flush()
    }
}

// Hasil tipikal:
// BenchmarkTanpaBufio:  ~15ms per operasi
// BenchmarkDenganBufio: ~1.5ms per operasi (~10x lebih cepat)

Kapan Beralih ke Alternatif #

Tetap gunakan bufio jika:
  ✓ Membaca file teks baris per baris — bufio.Scanner adalah pilihan terbaik
  ✓ Parsing format teks dengan delimiter kustom
  ✓ Menulis banyak data kecil ke file atau koneksi — bufio.Writer
  ✓ Perlu peek/unread saat parsing — bufio.Reader
  ✓ Parsing protokol teks (HTTP, SMTP, Redis RESP, dll)

Pertimbangkan os.ReadFile jika:
  ✗ File kecil yang bisa dimuat sekaligus ke memori
  ✗ Hanya butuh seluruh konten file, bukan per baris
  ✗ Operasi satu kali tanpa perlu streaming

Pertimbangkan encoding/csv jika:
  ✗ Parsing CSV yang benar dengan quoting dan escape
  ✗ CSV dengan field yang mengandung koma atau newline
  ✗ CSV standar yang kompleks — jangan parse manual dengan bufio

Pertimbangkan encoding/json dengan Decoder jika:
  ✗ Parsing JSON Lines (NDJSON) — json.Decoder sudah buffered
  ✗ JSON streaming dari HTTP body

Pertimbangkan io.Reader + io.Copy jika:
  ✗ Hanya perlu menyalin data dari satu stream ke stream lain
  ✗ io.Copy sudah melakukan buffering internal

Ringkasan #

  • bufio.Scanner untuk membaca per baris — API paling mudah, selalu periksa scanner.Err() setelah loop selesai untuk memastikan tidak ada error I/O.
  • scanner.Split(bufio.ScanWords) untuk membaca per kata, ScanBytes per byte, ScanRunes per rune — atau buat SplitFunc kustom untuk tokenisasi apapun.
  • scanner.Buffer(buf, max) untuk menangani baris yang lebih panjang dari 64KB default — tanpa ini, scanner akan error dengan “token too long” untuk baris panjang.
  • bufio.Writer untuk menulis banyak data kecil — bisa 10× lebih cepat dari menulis langsung ke file karena mengurangi jumlah system call secara drastis.
  • Selalu w.Flush() setelah selesai menulis dengan bufio.Writerdefer f.Close() tidak men-flush buffer secara otomatis, data di buffer akan hilang jika tidak di-flush.
  • bufio.Reader.Peek(n) untuk melihat data ke depan tanpa mengonsumsinya — berguna untuk deteksi format atau lookahead saat parsing protokol.
  • bufio.Reader.ReadString('\n') lebih mudah dari ReadLine()ReadString menangani baris panjang secara otomatis dan menyertakan delimiter di token.
  • bufio.NewReader untuk koneksi TCP — wrap net.Conn dengan bufio.Reader untuk parsing protokol teks yang efisien dan bufio.Writer untuk respons yang efisien.
  • bufio.Scanner mendukung io.Reader apapun — file, stdin, HTTP body, strings.Reader, bytes.Reader — API yang sama untuk semua sumber.

← Sebelumnya: Bytes   Berikutnya: Filepath →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact