Data yang masuk dari luar program — isi berkas, respons server, paket jaringan — hampir selalu tiba sebagai potongan []byte, bukan string. Kita tentu bisa mengubahnya menjadi string lalu memakai strings, tetapi konversi itu menyalin data setiap kali terjadi. Package bytes menyediakan jalan pintas: seluruh fungsi kegemaran kita dari package strings tersedia dalam wujud yang bekerja langsung pada []byte. Pada chapter ini kita akan membedakan kapan []byte lebih tepat daripada string, mencerminkan pola pemakaian API lama pada data baru, dan melihat konversi yang diam-diam gratis.
Data Mentah Bernama []byte #
Tipe []byte adalah slice biasa: isinya bisa diubah, dipotong, dan diperpanjang tanpa membuat salinan baru. Berlawanan dengan string yang immutable, []byte adalah wadah kerja untuk data mentah. Keduanya komplementer, dan tabel berikut membantu memilih:
| Aspek | string | []byte |
|---|---|---|
| Mutasi isi | Tidak bisa | Bisa di tempat |
Hasil baca os/io/jaringan | Perlu konversi | Bentuk asli |
| Kunci map dan field struct teks | Idiomatik | Jarang dipakai |
| Nilai balik fungsi publik bertipe teks | Idiomatik | Perlu alasan |
Aturan praktisnya: gunakan []byte selama masih memproses data (membaca, memfilter, memotong), lalu ubah ke string saat data tersebut akan disimpan, dibandingkan sebagai teks, atau dikembalikan ke pemanggil.
API yang Mencerminkan strings #
Fungsi di package bytes sengaja dibuat sebangun dengan strings — hanya tipe parameter yang berbeda. Pemanggilan berikut mencari substring di dalam potongan byte:
data := []byte("Halo, Budi")
fmt.Println(bytes.Contains(data, []byte("Budi")))Output yang Dihasilkan:
trueDaftar padanan yang paling sering terpakai:
Di strings | Cermin di bytes |
|---|---|
strings.Contains | bytes.Contains |
strings.HasPrefix / HasSuffix | bytes.HasPrefix / HasSuffix |
strings.Index | bytes.Index |
strings.ToUpper / ToLower | bytes.ToUpper / ToLower |
strings.Split / Join | bytes.Split / bytes.Join |
strings.TrimSpace | bytes.TrimSpace |
strings.EqualFold | bytes.EqualFold |
perbandingan s == t | bytes.Equal(b, c) |
Perlu diperhatikan bahwa bytes.Equal mengisi baris terakhir karena operator == tidak diizinkan untuk slice — hanya []byte yang dibandingkan dengan nil yang lolos, sebagaimana dibahas pada chapter Operator Perbandingan
. Fungsi bytes.ToUpper pun mengembalikan slice baru; data aslinya tidak berubah.
Konversi yang Murah #
Konversi string(b) dan []byte(s) pada umumnya menyalin isi byte. Compiler Go mengenal beberapa pengecualian penting dan menghapus salinan tersebut: pembandingan seperti string(b) == "kopi", iterasi for range string(b), dan — yang paling sering dipakai — lookup map harga[string(b)]. Pada program utuh di bawah, potongan []byte("kopi") dipakai sebagai kunci map string tanpa alokasi sama sekali.
bytes.Buffer #
Package ini juga memuat bytes.Buffer, tipe penampung byte yang tumbuh otomatis. Operasinya WriteString, WriteByte, lalu String() untuk mengambil hasilnya:
var buf bytes.Buffer
buf.WriteString("Halo")
buf.WriteByte(',')
buf.WriteString(" Budi")
fmt.Println(buf.String(), buf.Len())Output yang Dihasilkan:
Halo, Budi 10bytes.Buffer adalah tulang punggung banyak alat baca-tulis standard library, termasuk fmt. Untuk membangun string (bukan byte) secara efisien, Go modern menyediakan strings.Builder yang lebih ringan — bahasan chapter berikutnya. Adapun pola membaca data baris demi baris dari berkas dan jaringan memakai bufio.Scanner, dibahas tuntas di chapter bufio
.
Program Utuh #
Berkas bytes-demo.go mengolah data byte dari pencarian hingga lookup map:
package main
import (
"bytes"
"fmt"
)
func main() {
// Data mentah hasil pembacaan: potongan byte, bukan string.
data := []byte("Halo, Budi Santoso!")
// Fungsi bytes mencerminkan strings untuk []byte.
fmt.Println("Contains :", bytes.Contains(data, []byte("Budi")))
fmt.Println("HasPrefix:", bytes.HasPrefix(data, []byte("Halo")))
fmt.Println("Index :", bytes.Index(data, []byte("Santoso")))
fmt.Println("Equal :", bytes.Equal([]byte("go"), []byte("Go")))
fmt.Println("Equal :", bytes.Equal([]byte("go"), []byte("go")))
// ToLower menghasilkan salinan []byte yang baru.
fmt.Println("ToLower :", string(bytes.ToLower(data)))
// Split dan Join bekerja pada []byte.
nama := bytes.Split(data[6:], []byte(" "))
fmt.Println("Split :", nama)
for _, n := range nama {
fmt.Println("orang :", string(n))
}
fmt.Println("Join :", string(bytes.Join(nama, []byte(", "))))
// Trim membuang karakter tepi pada data mentah.
kotor := []byte(" \nbelajar go\t")
fmt.Printf("Trim : [%s]\n", bytes.TrimSpace(kotor))
// Lookup map dengan kunci []byte: konversi dioptimalkan compiler.
harga := map[string]int{"teh": 5000, "kopi": 12000}
if v, ok := harga[string([]byte("kopi"))]; ok {
fmt.Println("harga :", v)
}
}go run bytes-demo.goOutput yang Dihasilkan:
Contains : true
HasPrefix: true
Index : 11
Equal : false
Equal : true
ToLower : halo, budi santoso!
Split : [[66 117 100 105] [83 97 110 116 111 115 111 33]]
orang : Budi
orang : Santoso!
Join : Budi, Santoso!
Trim : [belajar go]
harga : 12000Mari kita telaah baris demi baris program di atas:
- Baris 10 (
data := []byte(...)): string literal dikonversi menjadi slice byte — meniru bentuk data yang keluar darios.ReadFileatauio.ReadAll. - Baris 13-17 (blok pemeriksaan):
Contains,HasPrefix, danIndexbekerja persis seperti distrings; dua barisEqualmenunjukkan perbandingan bersifat sensitif kapital (golawanGobernilaifalse). - Baris 20 (
bytes.ToLower): menghasilkan slice baru berisi huruf kecil, lalustring(...)mengubahnya agar tercetak sebagai teks. - Baris 23-28 (Split/Join):
data[6:]membuang enam byte pertama,Splitmemecah sisanya per spasi. BarisSplittercetak sebagai deretan angka karena elemennya memang[]byte— angka66 117 100 105adalah kode ASCII kataBudi. - Baris 31-32 (Trim): spasi, baris baru, dan tab di tepi data kotor dibuang tanpa membuat string perantara.
- Baris 35-38 (lookup map):
string([]byte("kopi"))dipakai langsung sebagai kunci mapharga; compiler menghapus salinannya sehingga harga12000ditemukan tanpa alokasi.
Kerja dengan []byte menuntut kita memikirkan alokasi. Kabar baiknya: untuk tugas paling umum — menggabungkan banyak potongan menjadi satu hasil — Go menyediakan alat khusus yang jauh lebih efisien daripada +=, yaitu strings.Builder pada chapter berikutnya.
Referensi #
- Dokumentasi package bytes — daftar lengkap fungsi cermin strings dan bytes.Buffer.
- Dokumentasi package bufio — pembacaan buffered baris demi baris.
- Blog Go: Strings, bytes, runes and characters — hubungan string, byte, dan rune.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
bytes-demo.go
Unduh berkaspackage main
import (
"bytes"
"fmt"
)
func main() {
// Data mentah hasil pembacaan: potongan byte, bukan string.
data := []byte("Halo, Budi Santoso!")
// Fungsi bytes mencerminkan strings untuk []byte.
fmt.Println("Contains :", bytes.Contains(data, []byte("Budi")))
fmt.Println("HasPrefix:", bytes.HasPrefix(data, []byte("Halo")))
fmt.Println("Index :", bytes.Index(data, []byte("Santoso")))
fmt.Println("Equal :", bytes.Equal([]byte("go"), []byte("Go")))
fmt.Println("Equal :", bytes.Equal([]byte("go"), []byte("go")))
// ToLower menghasilkan salinan []byte yang baru.
fmt.Println("ToLower :", string(bytes.ToLower(data)))
// Split dan Join bekerja pada []byte.
nama := bytes.Split(data[6:], []byte(" "))
fmt.Println("Split :", nama)
for _, n := range nama {
fmt.Println("orang :", string(n))
}
fmt.Println("Join :", string(bytes.Join(nama, []byte(", "))))
// Trim membuang karakter tepi pada data mentah.
kotor := []byte(" \nbelajar go\t")
fmt.Printf("Trim : [%s]\n", bytes.TrimSpace(kotor))
// Lookup map dengan kunci []byte: konversi dioptimalkan compiler.
harga := map[string]int{"teh": 5000, "kopi": 12000}
if v, ok := harga[string([]byte("kopi"))]; ok {
fmt.Println("harga :", v)
}
}