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:

Aspekstring[]byte
Mutasi isiTidak bisaBisa di tempat
Hasil baca os/io/jaringanPerlu konversiBentuk asli
Kunci map dan field struct teksIdiomatikJarang dipakai
Nilai balik fungsi publik bertipe teksIdiomatikPerlu 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:

go
data := []byte("Halo, Budi")
fmt.Println(bytes.Contains(data, []byte("Budi")))

Output yang Dihasilkan:

text
true

Daftar padanan yang paling sering terpakai:

Di stringsCermin di bytes
strings.Containsbytes.Contains
strings.HasPrefix / HasSuffixbytes.HasPrefix / HasSuffix
strings.Indexbytes.Index
strings.ToUpper / ToLowerbytes.ToUpper / ToLower
strings.Split / Joinbytes.Split / bytes.Join
strings.TrimSpacebytes.TrimSpace
strings.EqualFoldbytes.EqualFold
perbandingan s == tbytes.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:

go
var buf bytes.Buffer
buf.WriteString("Halo")
buf.WriteByte(',')
buf.WriteString(" Budi")
fmt.Println(buf.String(), buf.Len())

Output yang Dihasilkan:

text
Halo, Budi 10

bytes.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:

go
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)
	}
}
bash
go run bytes-demo.go

Output yang Dihasilkan:

text
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    : 12000

Mari kita telaah baris demi baris program di atas:

  1. Baris 10 (data := []byte(...)): string literal dikonversi menjadi slice byte — meniru bentuk data yang keluar dari os.ReadFile atau io.ReadAll.
  2. Baris 13-17 (blok pemeriksaan): Contains, HasPrefix, dan Index bekerja persis seperti di strings; dua baris Equal menunjukkan perbandingan bersifat sensitif kapital (go lawan Go bernilai false).
  3. Baris 20 (bytes.ToLower): menghasilkan slice baru berisi huruf kecil, lalu string(...) mengubahnya agar tercetak sebagai teks.
  4. Baris 23-28 (Split/Join): data[6:] membuang enam byte pertama, Split memecah sisanya per spasi. Baris Split tercetak sebagai deretan angka karena elemennya memang []byte — angka 66 117 100 105 adalah kode ASCII kata Budi.
  5. Baris 31-32 (Trim): spasi, baris baru, dan tab di tepi data kotor dibuang tanpa membuat string perantara.
  6. Baris 35-38 (lookup map): string([]byte("kopi")) dipakai langsung sebagai kunci map harga; compiler menghapus salinannya sehingga harga 12000 ditemukan 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 #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

bytes-demo.go

Unduh berkas
go
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)
	}
}