Setiap input dan output pada akhirnya hanyalah deretan byte yang berpindah tempat: dari berkas ke memori, dari koneksi jaringan ke layar, dari hasil kompresi ke program. Package io menangkap kesamaan tersebut menjadi dua interface kecil, io.Reader dan io.Writer, lalu membangun utilitas serba-guna di atasnya. Chapter ini membedah kontrak kedua interface, pola io.EOF, utilitas seperti Copy dan TeeReader, serta filosofi yang membuat seluruh I/O di Go saling kompatibel.
Dua Interface Inti: io.Reader dan io.Writer #
Seluruh sumber data di Go memenuhi io.Reader, dan seluruh tujuan data memenuhi io.Writer:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}Method Read menyalin paling banyak len(p) byte ke dalam slice p, lalu melaporkan berapa byte yang benar-benar terbaca melalui nilai n. Nilai n bisa lebih kecil dari len(p) tanpa itu menjadi galat, misalnya ketika data di jaringan belum semuanya tiba. Method Write bekerja secara terbalik: menerima p, menulis isinya, lalu melaporkan jumlah byte yang tertulis.
Kedua interface ini kecil sekali, tetapi justru ukurannya yang membuatnya berlipat-lipat. strings.Reader adalah Reader; os.File, isi http.Response.Body, dan pembungkus gzip.Reader juga Reader. Fungsi yang Anda tulis untuk satu tipe otomatis bekerja untuk semuanya.
io.EOF, Penanda Akhir Data #
Reader melaporkan habisnya data lewat error istimewa io.EOF (end of file). Pada akhir stream, Read mengembalikan n == 0 dan err == io.EOF. Sebagian implementasi juga boleh mengembalikan n > 0 sekaligus err == io.EOF pada pemanggilan yang sama, sehingga pola baca yang benar selalu memproses data lebih dulu, baru memeriksa error:
Utilitas io untuk Pemakaian Sehari-hari #
Daripada menulis loop Read manual, package io menyediakan utilitas yang menyusun pola-pola umum:
| Utilitas | Kegunaan |
|---|---|
io.Copy | Memindahkan seluruh data dari Reader ke Writer |
io.ReadAll | Membaca seluruh Reader menjadi []byte |
io.MultiReader | Menggabungkan beberapa Reader menjadi satu stream |
io.TeeReader | Menyalin setiap byte yang dibaca ke sebuah Writer |
io.LimitReader | Membatasi jumlah byte yang dapat dibaca |
io.Pipe | Menghubungkan goroutine penulis dan pembaca secara sinkron |
io.Discard | Writer pembuang data, cocok untuk menghitung jumlah byte |
io.Pipe berbeda dari lainnya: fungsi tersebut mengembalikan pasangan pembaca-penulis yang tersambung langsung, sehingga data yang ditulis di satu goroutine langsung terbaca di goroutine lain — pola yang muncul lagi di chapter Concurrency
.
Program Utuh #
Berkas io-demo.go mempraktikkan utilitas-utilitas tersebut dengan strings.NewReader sebagai sumber data yang deterministik:
package main
import (
"fmt"
"io"
"strings"
)
func main() {
// io.LimitReader membatasi jumlah byte yang bisa dibaca.
terbatas := io.LimitReader(strings.NewReader("belajar io di Go"), 8)
data, _ := io.ReadAll(terbatas)
fmt.Printf("limit 8 byte: %q\n", data)
// io.MultiReader menggabungkan beberapa sumber menjadi satu.
gabungan := io.MultiReader(
strings.NewReader("belajar "),
strings.NewReader("io "),
strings.NewReader("mudah"),
)
utuh, _ := io.ReadAll(gabungan)
fmt.Printf("multi reader: %q\n", utuh)
// io.TeeReader menyalin setiap byte yang dibaca.
var salinan strings.Builder
sumber := io.TeeReader(strings.NewReader("data penting"), &salinan)
jumlah, _ := io.Copy(io.Discard, sumber)
fmt.Printf("tee: %d byte terbaca, salinan: %q\n", jumlah, salinan.String())
// Pola baca manual dengan io.EOF sebagai penanda akhir data.
pembaca := strings.NewReader("AB")
penyangga := make([]byte, 1)
for {
n, err := pembaca.Read(penyangga)
if n > 0 {
fmt.Printf("terbaca %d byte: %q\n", n, string(penyangga[:n]))
}
if err == io.EOF {
fmt.Println("baca selesai (io.EOF)")
break
}
}
}Jalankan program dari folder chapter:
go run io-demo.goOutput yang Dihasilkan:
limit 8 byte: "belajar "
multi reader: "belajar io mudah"
tee: 12 byte terbaca, salinan: "data penting"
terbaca 1 byte: "A"
terbaca 1 byte: "B"
baca selesai (io.EOF)Mari kita telaah baris demi baris program di atas:
- Baris 11-13:
io.LimitReadermembungkus sumber 16 byte dan hanya mengizinkan 8 byte terbaca, sehinggaio.ReadAllberhenti pada"belajar "— spasi termasuk hitungan byte. - Baris 16-22:
io.MultiReadermerangkai tiga sumber menjadi satu stream;io.ReadAllmembacanya seolah berasal dari satu Reader. - Baris 25-28:
io.TeeReadermenyalin setiap byte yang dibaca kesalinanbertipestrings.Builder;io.Copymemindahkan seluruh data keio.Discardsehingga yang tersisa hanyalah hitunganjumlahdan isi salinan. - Baris 31-32: pembacaan manual disiapkan dengan sumber dua byte dan penyangga satu byte.
- Baris 34-37: loop memproses
n > 0terlebih dahulu — sesuai kontrakRead— sebelum memeriksa error. - Baris 38-40:
io.EOFmenjadi sinyal berhenti; program keluar dari loop dengan rapi.
Pada tiga pemanggilan pertama, error sengaja diabaikan dengan _ karena sumbernya strings.Reader yang tak mungkin gagal; pada program sungguhan, error io.Copy dan io.ReadAll tetap layak diperiksa.
Semua Hal adalah Reader dan Writer #
Filosofi “everything is a Reader/Writer” inilah yang membuat package io terasa kecil namun kuat. Sebuah fungsi penghitung kata yang menerima io.Reader bekerja untuk teks di memori, isi berkas, respons HTTP, maupun hasil dekompresi — tanpa perubahan kode:
| Peran | Contoh tipe |
|---|---|
Sumber (Reader) | strings.Reader, os.File, http.Response.Body, gzip.Reader |
Tujuan (Writer) | strings.Builder, os.Stdout, os.File, gzip.Writer |
Referensi #
- Dokumentasi package io — kontrak Reader, Writer, dan seluruh utilitas.
- io.Copy — mekanisme pemindahan data antar stream.
- io.EOF — semantics penanda akhir data.
- io.MultiReader — penggabungan beberapa sumber.
Pembacaan byte demi byte seperti di atas bekerja, tetapi satu panggilan sistem operasi untuk setiap Read membuatnya lambat. Chapter berikutnya memperkenalkan package bufio: lapisan penyangga yang menggabungkan banyak bacaan kecil menjadi operasi besar.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
io-demo.go
Unduh berkaspackage main
import (
"fmt"
"io"
"strings"
)
func main() {
// io.LimitReader membatasi jumlah byte yang bisa dibaca.
terbatas := io.LimitReader(strings.NewReader("belajar io di Go"), 8)
data, _ := io.ReadAll(terbatas)
fmt.Printf("limit 8 byte: %q\n", data)
// io.MultiReader menggabungkan beberapa sumber menjadi satu.
gabungan := io.MultiReader(
strings.NewReader("belajar "),
strings.NewReader("io "),
strings.NewReader("mudah"),
)
utuh, _ := io.ReadAll(gabungan)
fmt.Printf("multi reader: %q\n", utuh)
// io.TeeReader menyalin setiap byte yang dibaca.
var salinan strings.Builder
sumber := io.TeeReader(strings.NewReader("data penting"), &salinan)
jumlah, _ := io.Copy(io.Discard, sumber)
fmt.Printf("tee: %d byte terbaca, salinan: %q\n", jumlah, salinan.String())
// Pola baca manual dengan io.EOF sebagai penanda akhir data.
pembaca := strings.NewReader("AB")
penyangga := make([]byte, 1)
for {
n, err := pembaca.Read(penyangga)
if n > 0 {
fmt.Printf("terbaca %d byte: %q\n", n, string(penyangga[:n]))
}
if err == io.EOF {
fmt.Println("baca selesai (io.EOF)")
break
}
}
}