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:

go
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:

UtilitasKegunaan
io.CopyMemindahkan seluruh data dari Reader ke Writer
io.ReadAllMembaca seluruh Reader menjadi []byte
io.MultiReaderMenggabungkan beberapa Reader menjadi satu stream
io.TeeReaderMenyalin setiap byte yang dibaca ke sebuah Writer
io.LimitReaderMembatasi jumlah byte yang dapat dibaca
io.PipeMenghubungkan goroutine penulis dan pembaca secara sinkron
io.DiscardWriter 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:

go
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:

bash
go run io-demo.go

Output yang Dihasilkan:

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

  1. Baris 11-13: io.LimitReader membungkus sumber 16 byte dan hanya mengizinkan 8 byte terbaca, sehingga io.ReadAll berhenti pada "belajar " — spasi termasuk hitungan byte.
  2. Baris 16-22: io.MultiReader merangkai tiga sumber menjadi satu stream; io.ReadAll membacanya seolah berasal dari satu Reader.
  3. Baris 25-28: io.TeeReader menyalin setiap byte yang dibaca ke salinan bertipe strings.Builder; io.Copy memindahkan seluruh data ke io.Discard sehingga yang tersisa hanyalah hitungan jumlah dan isi salinan.
  4. Baris 31-32: pembacaan manual disiapkan dengan sumber dua byte dan penyangga satu byte.
  5. Baris 34-37: loop memproses n > 0 terlebih dahulu — sesuai kontrak Read — sebelum memeriksa error.
  6. Baris 38-40: io.EOF menjadi 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:

PeranContoh tipe
Sumber (Reader)strings.Reader, os.File, http.Response.Body, gzip.Reader
Tujuan (Writer)strings.Builder, os.Stdout, os.File, gzip.Writer

Referensi #

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