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:#f3e5f5bufio.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 callsimport (
"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 panggilw.Flush()sebelum file ditutup.defer f.Close()tidak akan men-flushbufio.Writersecara 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:#e8f5e9import (
"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.Scanneruntuk membaca per baris — API paling mudah, selalu periksascanner.Err()setelah loop selesai untuk memastikan tidak ada error I/O.scanner.Split(bufio.ScanWords)untuk membaca per kata,ScanBytesper byte,ScanRunesper rune — atau buatSplitFunckustom 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.Writeruntuk 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 denganbufio.Writer—defer 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 dariReadLine()—ReadStringmenangani baris panjang secara otomatis dan menyertakan delimiter di token.bufio.NewReaderuntuk koneksi TCP — wrapnet.Conndenganbufio.Readeruntuk parsing protokol teks yang efisien danbufio.Writeruntuk respons yang efisien.bufio.Scannermendukungio.Readerapapun — file, stdin, HTTP body, strings.Reader, bytes.Reader — API yang sama untuk semua sumber.