Bytes #
Package bytes adalah cermin dari package strings — hampir semua fungsi yang ada di strings punya padanannya di bytes, tapi bekerja pada []byte alih-alih string. Ini penting karena di Go, konversi antara string dan []byte selalu menghasilkan salinan baru di memori. Kode yang banyak melakukan konversi bolak-balik []byte ↔ string akan membebani garbage collector. Package bytes memungkinkan kamu bekerja langsung dengan []byte tanpa konversi yang tidak perlu — terutama penting saat memproses data dari jaringan, file, atau stream yang secara natural berbentuk byte. Di samping fungsi manipulasi, bytes.Buffer adalah tool yang sangat sering dipakai untuk membangun []byte atau string secara inkremental dengan efisien.
Gambaran Besar Package bytes #
flowchart TD
B["package bytes"] --> Func["Fungsi Manipulasi\n(mirip strings)"]
B --> Buffer["bytes.Buffer\nbangun data inkremental"]
B --> Reader["bytes.Reader\nio.Reader dari []byte"]
Func --> Search["Pencarian\nContains / Index / Count\nIndexByte / IndexRune"]
Func --> Transform["Transformasi\nToUpper / ToLower / Title\nTrimSpace / Trim / Replace"]
Func --> Split["Pemisahan & Gabungan\nSplit / SplitN / Fields\nJoin / Repeat"]
Func --> Compare["Perbandingan\nEqual / Compare\nHasPrefix / HasSuffix"]
Buffer --> BW["io.Writer\nWrite / WriteByte / WriteString\nWriteRune"]
Buffer --> BR["io.Reader\nRead / ReadByte / ReadRune\nReadString / ReadLine"]
Buffer --> BA["Akses\nBytes() / String()\nLen() / Cap() / Reset()"]
Reader --> RR["io.Reader, io.Seeker\nio.ReaderAt\nSeek / ReadAt"]
style B fill:#4f86c6,color:#fff
style Func fill:#e8f5e9
style Buffer fill:#e3f2fd
style Reader fill:#fff3e0Fungsi Dasar — Pencarian dan Pemeriksaan #
package main
import (
"bytes"
"fmt"
)
func main() {
data := []byte("Halo, Dunia! Halo, Go!")
// Contains — apakah mengandung sub-slice?
fmt.Println(bytes.Contains(data, []byte("Dunia"))) // true
fmt.Println(bytes.Contains(data, []byte("Python"))) // false
// ContainsAny — mengandung byte apapun dari string?
fmt.Println(bytes.ContainsAny(data, "aeiou")) // true — ada vokal
fmt.Println(bytes.ContainsAny(data, "xyz")) // false
// ContainsRune — mengandung rune tertentu?
fmt.Println(bytes.ContainsRune(data, '!')) // true
// Count — hitung kemunculan
fmt.Println(bytes.Count(data, []byte("Halo"))) // 2
fmt.Println(bytes.Count(data, []byte(""))) // 23 (len+1)
// Index — posisi kemunculan pertama (-1 jika tidak ada)
fmt.Println(bytes.Index(data, []byte("Dunia"))) // 6
fmt.Println(bytes.Index(data, []byte("Java"))) // -1
// LastIndex — posisi kemunculan terakhir
fmt.Println(bytes.LastIndex(data, []byte("Halo"))) // 14
// IndexByte — cari satu byte (lebih cepat dari Index)
fmt.Println(bytes.IndexByte(data, '!')) // 12
// IndexRune — cari satu rune (mendukung multibyte)
fmt.Println(bytes.IndexRune(data, 'D')) // 6
// IndexAny — cari byte apapun dari set
fmt.Println(bytes.IndexAny(data, "aeiou")) // 1 — 'a' di "Halo"
// HasPrefix dan HasSuffix
fmt.Println(bytes.HasPrefix(data, []byte("Halo"))) // true
fmt.Println(bytes.HasSuffix(data, []byte("Go!"))) // true
}
Transformasi — Mengubah Isi Byte Slice #
data := []byte(" Halo, Dunia! ")
// Trim — hapus karakter dari kedua ujung
fmt.Println(string(bytes.TrimSpace(data))) // "Halo, Dunia!"
fmt.Println(string(bytes.Trim(data, " !"))) // "Halo, Dunia"
fmt.Println(string(bytes.TrimLeft(data, " "))) // "Halo, Dunia! "
fmt.Println(string(bytes.TrimRight(data, " "))) // " Halo, Dunia!"
fmt.Println(string(bytes.TrimPrefix(data, []byte(" ")))) // "Halo, Dunia! "
fmt.Println(string(bytes.TrimSuffix(data, []byte(" ")))) // " Halo, Dunia!"
// TrimFunc — hapus byte yang memenuhi kondisi
clean := bytes.TrimFunc(data, func(r rune) bool {
return r == ' ' || r == '!'
})
fmt.Println(string(clean)) // "Halo, Dunia"
// Case conversion
s := []byte("halo dunia")
fmt.Println(string(bytes.ToUpper(s))) // "HALO DUNIA"
fmt.Println(string(bytes.ToLower([]byte("HALO DUNIA")))) // "halo dunia"
fmt.Println(string(bytes.ToTitle(s))) // "HALO DUNIA" (title = upper untuk ASCII)
// Title — kapitalkan awal kata (deprecated di strings, tapi ada di bytes)
fmt.Println(string(bytes.Title(s))) // "Halo Dunia"
// Replace dan ReplaceAll
teks := []byte("kucing makan ikan, kucing senang")
fmt.Println(string(bytes.Replace(teks, []byte("kucing"), []byte("anjing"), 1)))
// "anjing makan ikan, kucing senang" — ganti hanya pertama
fmt.Println(string(bytes.ReplaceAll(teks, []byte("kucing"), []byte("anjing"))))
// "anjing makan ikan, anjing senang" — ganti semua
// Map — transformasi per rune
hasil := bytes.Map(func(r rune) rune {
if r >= 'a' && r <= 'z' {
return r - 32 // lowercase ke uppercase
}
return r
}, []byte("halo, dunia!"))
fmt.Println(string(hasil)) // "HALO, DUNIA!"
Pemisahan dan Penggabungan #
// Split — pisahkan dengan separator
data := []byte("apel,mangga,jeruk,pisang")
bagian := bytes.Split(data, []byte(","))
for _, b := range bagian {
fmt.Println(string(b))
}
// apel
// mangga
// jeruk
// pisang
// SplitN — maksimal N bagian
bagian2 := bytes.SplitN(data, []byte(","), 2)
fmt.Println(string(bagian2[0])) // "apel"
fmt.Println(string(bagian2[1])) // "mangga,jeruk,pisang"
// SplitAfter — separator ikut di bagian sebelumnya
bagian3 := bytes.SplitAfter(data, []byte(","))
// ["apel," "mangga," "jeruk," "pisang"]
// Fields — pisahkan berdasarkan whitespace (mirip strings.Fields)
kalimat := []byte(" halo dunia go ")
kata := bytes.Fields(kalimat)
for _, k := range kata {
fmt.Printf("[%s]\n", k)
}
// [halo]
// [dunia]
// [go]
// FieldsFunc — pisahkan berdasarkan kondisi
csv := []byte("satu,,dua,,,tiga")
kolom := bytes.FieldsFunc(csv, func(r rune) bool {
return r == ','
})
// ["satu" "dua" "tiga"] — field kosong dilewati
// Join — gabungkan dengan separator
buah := [][]byte{[]byte("apel"), []byte("mangga"), []byte("jeruk")}
hasil := bytes.Join(buah, []byte(", "))
fmt.Println(string(hasil)) // "apel, mangga, jeruk"
// Repeat — ulangi byte slice
fmt.Println(string(bytes.Repeat([]byte("ab"), 4))) // "abababab"
fmt.Println(string(bytes.Repeat([]byte("-"), 20))) // "--------------------"
Perbandingan Byte Slice #
a := []byte("apel")
b := []byte("mangga")
c := []byte("apel")
// Equal — apakah isinya sama?
fmt.Println(bytes.Equal(a, c)) // true
fmt.Println(bytes.Equal(a, b)) // false
// ANTI-PATTERN: bandingkan dengan string()
// Ini membuat alokasi salinan string!
fmt.Println(string(a) == string(c)) // true tapi tidak efisien
// BENAR: gunakan bytes.Equal
fmt.Println(bytes.Equal(a, c)) // true tanpa alokasi
// Compare — seperti strcmp: -1, 0, atau 1
fmt.Println(bytes.Compare(a, b)) // -1 (apel < mangga)
fmt.Println(bytes.Compare(b, a)) // 1 (mangga > apel)
fmt.Println(bytes.Compare(a, c)) // 0 (sama)
// EqualFold — bandingkan case-insensitive
x := []byte("Halo")
y := []byte("halo")
fmt.Println(bytes.EqualFold(x, y)) // true
bytes.Buffer — Membangun Data Secara Inkremental #
bytes.Buffer adalah tool yang paling sering dipakai dari package bytes. Ia mengimplementasikan io.Reader dan io.Writer, membuatnya sangat fleksibel untuk membangun data byte secara inkremental tanpa banyak alokasi.
flowchart LR
subgraph Write["Menulis ke Buffer"]
W1["Write([]byte)\ntulis byte slice"]
W2["WriteByte(byte)\ntulis satu byte"]
W3["WriteString(string)\ntulis string"]
W4["WriteRune(rune)\ntulis satu rune"]
W5["fmt.Fprintf(&buf, ...)\ntulis dengan format"]
end
subgraph Buffer["bytes.Buffer"]
B["internal\n[]byte"]
end
subgraph Read["Membaca dari Buffer"]
R1["Read([]byte)\nbaca ke slice"]
R2["ReadByte()\nbaca satu byte"]
R3["ReadRune()\nbaca satu rune"]
R4["ReadString('\\n')\nbaca sampai delimiter"]
R5["ReadLine()\nbaca satu baris"]
end
subgraph Access["Akses Data"]
A1["Bytes() []byte\nisi buffer (tidak salin)"]
A2["String() string\nisi sebagai string"]
A3["Len() int\njumlah byte tersisa"]
A4["Reset()\nkosongkan buffer"]
end
Write --> Buffer
Buffer --> Read
Buffer --> Accessimport (
"bytes"
"fmt"
)
// Dasar: membangun byte slice
var buf bytes.Buffer
buf.WriteString("Halo, ")
buf.WriteString("Dunia")
buf.WriteByte('!')
buf.WriteRune('🌍')
fmt.Println(buf.String()) // "Halo, Dunia!🌍"
fmt.Println(buf.Len()) // 17 (dalam byte, bukan rune)
// Gunakan fmt.Fprintf untuk format
var buf2 bytes.Buffer
for i := 1; i <= 5; i++ {
fmt.Fprintf(&buf2, "item %d\n", i)
}
fmt.Print(buf2.String())
// item 1
// item 2
// item 3
// item 4
// item 5
// Reset dan reuse
buf.Reset()
fmt.Println(buf.Len()) // 0
fmt.Println(buf.Cap()) // kapasitas masih ada, tidak direalokasi
// Inisialisasi dengan konten awal
buf3 := bytes.NewBuffer([]byte("data awal"))
buf3.WriteString(" ditambah")
fmt.Println(buf3.String()) // "data awal ditambah"
// Membaca dari buffer
buf4 := bytes.NewBuffer([]byte("baris pertama\nbaris kedua\nbaris ketiga\n"))
line, err := buf4.ReadString('\n')
fmt.Print(line) // "baris pertama\n"
fmt.Println(err) // nil
line, err = buf4.ReadString('\n')
fmt.Print(line) // "baris kedua\n"
Buffer vs strings.Builder — Kapan Mana #
flowchart TD
Q{"Tujuan output?"} --> S["Hanya butuh string\npada akhirnya"]
Q --> B["Butuh []byte\natau keduanya"]
Q --> IO["Butuh io.Reader\natau io.Writer"]
S --> SB["strings.Builder\nlebih efisien untuk string\ntidak bisa Read"]
B --> BB["bytes.Buffer\nfleksibel: bisa baca dan tulis\nbisa jadi io.Reader/Writer"]
IO --> BB2["bytes.Buffer\nimplementasi keduanya"]
SB --> SE["String() untuk ambil hasil"]
BB --> BE["Bytes() atau String()\nuntuk ambil hasil"]
style SB fill:#e8f5e9
style BB fill:#e3f2fd
style BB2 fill:#e3f2fd// strings.Builder — untuk membangun string (tidak bisa dibaca sebagai Reader)
var sb strings.Builder
sb.WriteString("Halo, ")
sb.WriteString("Dunia!")
hasil := sb.String() // ambil hasil sebagai string
// bytes.Buffer — untuk membangun []byte atau saat butuh io.Reader
var buf bytes.Buffer
buf.WriteString("data protokol")
buf.WriteByte(0x00) // bisa tulis byte apapun termasuk null
// Kirim sebagai io.Reader ke fungsi lain
json.NewDecoder(&buf).Decode(&target)
http.Post(url, "application/octet-stream", &buf)
// ANTI-PATTERN: gunakan bytes.Buffer hanya untuk string akhir
var buf2 bytes.Buffer
for i := 0; i < 100; i++ {
buf2.WriteString("item") // Buffer bisa, tapi Builder lebih efisien
}
_ = buf2.String()
// BENAR: strings.Builder untuk pure string building
var sb2 strings.Builder
for i := 0; i < 100; i++ {
sb2.WriteString("item")
}
_ = sb2.String()
bytes.Reader — io.Reader dari []byte #
bytes.Reader mengubah []byte menjadi io.Reader yang mendukung seeking — berguna saat kamu punya data di memori tapi fungsi yang akan menerimanya mengharapkan io.Reader:
data := []byte(`{"nama":"Budi","umur":30}`)
// Buat Reader dari []byte
reader := bytes.NewReader(data)
// Decode JSON dari Reader (bukan dari []byte langsung)
var pengguna struct {
Nama string `json:"nama"`
Umur int `json:"umur"`
}
json.NewDecoder(reader).Decode(&pengguna)
fmt.Println(pengguna.Nama, pengguna.Umur) // Budi 30
// Seek — kembali ke posisi tertentu
reader.Seek(0, 0) // kembali ke awal
fmt.Println(reader.Len()) // 25 — kembali ke panjang penuh
// ReadAt — baca dari posisi tertentu tanpa menggeser posisi
buf := make([]byte, 4)
reader.ReadAt(buf, 2)
fmt.Println(string(buf)) // "\"nam"
// Ukuran
fmt.Println(reader.Size()) // 25 — ukuran total (tidak berubah setelah Seek)
// Gunakan sebagai io.Reader untuk HTTP upload
data2 := []byte("konten file ini")
resp, err := http.Post(
"https://api.example.com/upload",
"application/octet-stream",
bytes.NewReader(data2),
)
_ = resp
_ = err
Bekerja dengan Data Biner #
Package bytes sangat berguna saat memproses data biner — protokol jaringan, format file, atau stream data yang berisi campuran teks dan binary:
import (
"bytes"
"encoding/binary"
"fmt"
)
// Parsing frame protokol sederhana:
// [4 byte panjang][1 byte tipe][N byte payload]
func parseFrame(data []byte) (tipe byte, payload []byte, err error) {
if len(data) < 5 {
return 0, nil, fmt.Errorf("frame terlalu pendek: %d byte", len(data))
}
reader := bytes.NewReader(data)
// Baca panjang payload (4 byte big-endian)
var panjang uint32
if err := binary.Read(reader, binary.BigEndian, &panjang); err != nil {
return 0, nil, fmt.Errorf("baca panjang: %w", err)
}
// Baca tipe (1 byte)
tipeByte, err := reader.ReadByte()
if err != nil {
return 0, nil, fmt.Errorf("baca tipe: %w", err)
}
// Baca payload
payload = make([]byte, panjang)
if _, err := reader.Read(payload); err != nil {
return 0, nil, fmt.Errorf("baca payload: %w", err)
}
return tipeByte, payload, nil
}
// Membangun frame protokol
func buatFrame(tipe byte, payload []byte) []byte {
var buf bytes.Buffer
// Tulis panjang payload (4 byte big-endian)
binary.Write(&buf, binary.BigEndian, uint32(len(payload)))
// Tulis tipe
buf.WriteByte(tipe)
// Tulis payload
buf.Write(payload)
return buf.Bytes()
}
// Penggunaan
frame := buatFrame(0x01, []byte("Halo dari Go!"))
tipe, payload, err := parseFrame(frame)
if err == nil {
fmt.Printf("Tipe: 0x%02X, Payload: %s\n", tipe, payload)
}
Memproses HTTP Response Body #
import (
"bytes"
"compress/gzip"
"io"
"net/http"
)
func ambilDanProses(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("GET %s: %w", url, err)
}
defer resp.Body.Close()
// Baca seluruh body ke buffer
var buf bytes.Buffer
if _, err := io.Copy(&buf, resp.Body); err != nil {
return nil, fmt.Errorf("baca body: %w", err)
}
data := buf.Bytes()
// Periksa apakah gzip-encoded
if bytes.HasPrefix(data, []byte{0x1f, 0x8b}) {
// Magic bytes gzip
reader, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("buka gzip: %w", err)
}
defer reader.Close()
var decompressed bytes.Buffer
if _, err := io.Copy(&decompressed, reader); err != nil {
return nil, fmt.Errorf("decompress: %w", err)
}
return decompressed.Bytes(), nil
}
return data, nil
}
Pola Penggunaan di Produksi #
Template Rendering ke Buffer #
import (
"bytes"
"html/template"
)
var tmplEmail = template.Must(template.New("email").Parse(`
Kepada: {{.Nama}}
Terima kasih telah mendaftar di layanan kami.
Kode verifikasi kamu: {{.Kode}}
Kode ini berlaku selama {{.DurasiMenit}} menit.
`))
type DataEmail struct {
Nama string
Kode string
DurasiMenit int
}
func renderEmail(data DataEmail) ([]byte, error) {
var buf bytes.Buffer
if err := tmplEmail.Execute(&buf, data); err != nil {
return nil, fmt.Errorf("render email: %w", err)
}
return buf.Bytes(), nil
}
// Penggunaan
konten, err := renderEmail(DataEmail{
Nama: "Budi",
Kode: "123456",
DurasiMenit: 10,
})
if err == nil {
fmt.Println(string(konten))
}
Membangun CSV Manual #
func buatCSV(headers []string, rows [][]string) []byte {
var buf bytes.Buffer
// Tulis header
for i, h := range headers {
if i > 0 {
buf.WriteByte(',')
}
buf.WriteString(escapeCSV(h))
}
buf.WriteByte('\n')
// Tulis baris data
for _, row := range rows {
for i, cell := range row {
if i > 0 {
buf.WriteByte(',')
}
buf.WriteString(escapeCSV(cell))
}
buf.WriteByte('\n')
}
return buf.Bytes()
}
func escapeCSV(s string) string {
// Kutip jika mengandung koma, newline, atau kutip
if bytes.ContainsAny([]byte(s), ",\"\n\r") {
return `"` + strings.ReplaceAll(s, `"`, `""`) + `"`
}
return s
}
// Penggunaan
csv := buatCSV(
[]string{"ID", "Nama", "Email", "Kota"},
[][]string{
{"1", "Budi Santoso", "[email protected]", "Jakarta"},
{"2", "Ani", "[email protected]", "Bandung, Jawa Barat"},
{"3", "Charlie", `char"[email protected]`, "Surabaya"},
},
)
os.WriteFile("output.csv", csv, 0644)
Buffer Pool dengan sync.Pool #
import "sync"
// Pool bytes.Buffer untuk menghindari alokasi berulang
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
func ambilBuffer() *bytes.Buffer {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset() // PENTING: reset sebelum digunakan
return buf
}
func kembalikanBuffer(buf *bytes.Buffer) {
// Jangan kembalikan buffer yang terlalu besar ke pool
// untuk menghindari menahan memori berlebih
if buf.Cap() <= 64*1024 { // 64 KB
bufPool.Put(buf)
}
}
func prosesRequest(data []byte) string {
buf := ambilBuffer()
defer kembalikanBuffer(buf)
// Gunakan buf untuk membangun response
buf.WriteString(`{"status":"ok","data":`)
buf.Write(data)
buf.WriteByte('}')
return buf.String()
}
Streaming Data Besar tanpa Load ke Memori #
// Proses file besar baris per baris tanpa load semua ke memori
func prosesFileBesar(path string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("buka file: %w", err)
}
defer f.Close()
var (
buf = make([]byte, 64*1024) // buffer 64 KB
leftover []byte // sisa dari pembacaan sebelumnya
baris int
)
for {
n, err := f.Read(buf)
if n > 0 {
// Gabungkan sisa sebelumnya dengan data baru
chunk := append(leftover, buf[:n]...)
leftover = nil
// Proses setiap baris lengkap
for {
idx := bytes.IndexByte(chunk, '\n')
if idx < 0 {
// Tidak ada newline — simpan sebagai sisa
leftover = chunk
break
}
baris++
prosesBarisData(chunk[:idx])
chunk = chunk[idx+1:]
}
}
if err == io.EOF {
// Proses sisa akhir jika tidak diakhiri newline
if len(leftover) > 0 {
baris++
prosesBarisData(leftover)
}
break
}
if err != nil {
return fmt.Errorf("baca file: %w", err)
}
}
fmt.Printf("Diproses %d baris\n", baris)
return nil
}
bytes vs strings — Kapan Mana #
flowchart TD
Q{"Data yang dikerjakan?"} --> Str["string\n(immutable, sudah ada)"]
Q --> ByteSlice["[]byte\n(mutable, dari jaringan/file)"]
Q --> Both["Campuran keduanya"]
Str --> UseStr["Gunakan package strings\n+ strings.Builder\ntanpa konversi"]
ByteSlice --> UseBytes["Gunakan package bytes\n+ bytes.Buffer\ntanpa konversi"]
Both --> Consider["Tentukan representasi utama\nminimalkan konversi\nbytes → string: string(b)\nstring → bytes: []byte(s)"]
Consider --> Rule["Aturan: konversi menghasilkan salinan\nJika fungsi butuh []byte, hindari\nstring → []byte → string"]
style UseStr fill:#e8f5e9
style UseBytes fill:#e3f2fd
style Rule fill:#fff3e0// ANTI-PATTERN: konversi bolak-balik yang tidak perlu
func prosesDataBuruk(data []byte) []byte {
s := string(data) // salinan pertama: []byte → string
s = strings.ToUpper(s) // proses
s = strings.TrimSpace(s) // proses
return []byte(s) // salinan kedua: string → []byte
}
// BENAR: tetap di []byte, gunakan bytes package
func prosesDataBaik(data []byte) []byte {
result := bytes.ToUpper(data)
return bytes.TrimSpace(result)
}
// Kapan konversi MEMANG diperlukan:
// 1. Map/switch case dengan string literal
switch string(data[:4]) {
case "HTTP", "POST", "GET ":
// proses
}
// 2. Saat fungsi eksternal hanya menerima string
log.Println(string(data)) // log.Println butuh string
// 3. Saat menyimpan ke struct yang bertipe string
pengguna.Nama = string(namaBytes)
Kapan Beralih ke Alternatif #
Tetap gunakan bytes jika:
✓ Manipulasi []byte: pencarian, trim, split, replace
✓ bytes.Buffer untuk membangun []byte secara inkremental
✓ bytes.Reader untuk meneruskan []byte sebagai io.Reader
✓ Pemrosesan data biner (protokol, format file)
✓ Bekerja dengan data dari jaringan atau file yang secara natural berupa []byte
Gunakan strings jika:
✗ Data sudah dalam bentuk string dan akan tetap string
✗ Manipulasi string: strings.Contains, strings.Split, dll
Gunakan strings.Builder jika:
✗ Membangun string tanpa butuh io.Reader/Writer
✗ Lebih efisien dari bytes.Buffer untuk pure string output
Gunakan bufio jika:
✗ Buffered I/O dari file atau koneksi jaringan
✗ Membaca per baris dari stream yang besar
✗ Parsing teks baris per baris dengan Scanner
Gunakan encoding/binary jika:
✗ Membaca/menulis integer dengan endianness tertentu dari []byte
✗ Parsing format biner yang terstruktur
Ringkasan #
bytesadalah cerminstrings— hampir semua fungsistringsada padanannya dibytes. Pilih berdasarkan tipe data:string→strings,[]byte→bytes.- Hindari konversi
[]byte↔stringyang tidak perlu — setiap konversi menghasilkan salinan di memori. Jika data datang sebagai[]byte, proses sebagai[]bytesampai selesai.bytes.Equal(a, b)lebih efisien daristring(a) == string(b)— tidak ada alokasi string sementara, bandingkan langsung byte per byte.bytes.Buffermengimplementasikanio.Readerdanio.Writer— berguna saat perlu buffer yang bisa dibaca setelah ditulis, atau saat fungsi eksternal membutuhkanio.Reader.strings.Builderuntuk pure string building — lebih efisien daribytes.Buffersaat output akhir adalahstringdan tidak perlu membaca kembali sebagaiReader.bytes.Readeruntuk mengubah[]bytemenjadiio.Reader— mendukung seeking (Seek), berguna untuk rewind atau pembacaan ulang dari posisi berbeda.- Gunakan
sync.Pooluntukbytes.Bufferdi hot path HTTP handler — hindari alokasi buffer baru setiap request dengan mendaur ulang buffer yang sudah ada.bytes.Buffer.Reset()mengosongkan buffer tanpa melepas kapasitas yang sudah dialokasikan — reuse buffer dengan Reset jauh lebih efisien dari membuat buffer baru.- Untuk data besar, proses secara streaming dengan buffer kecil daripada load semua ke memori — gunakan
io.Copyatau loop baca manual dengan[]bytesebagai buffer.