Hampir semua program yang berguna mengambil data dari luar memorinya: berkas konfigurasi, hasil ekspor sistem lain, atau unggahan pengguna. Package os menyediakan beberapa cara membaca berkas, dan perbedaannya terletak pada siapa yang mengelola memori serta kapan berkas ditutup. Pada chapter ini kita membandingkan empat pola baca — os.ReadFile, os.Open + io.ReadAll, bufio.Scanner, dan Read per potongan — lalu menangani berkas yang tidak ada dengan errors.Is.
Membaca Seluruh Isi Sekaligus #
Fungsi os.ReadFile menerima satu nama berkas, membuka berkas itu, memuat seluruh isinya ke satu slice []byte, lalu menutup berkas secara otomatis. Pemanggil tidak mengurus handle sama sekali:
data, err := os.ReadFile("catatan.txt")
if err != nil {
fmt.Println("gagal membaca berkas:", err)
return
}
fmt.Println(len(data), "byte")Bentuk ini paling ringkas untuk berkas kecil yang memang ingin dimuat seluruhnya. Batasannya: seluruh isi menempati memori sekaligus, jadi pola ini tidak cocok untuk berkas ratusan megabyte.
Membuka Berkas dengan os.Open #
Ketika kita butuh kendali lebih — misalnya membaca per bagian — os.Open menghasilkan nilai *os.File yang berperan sebagai handle menuju berkas terbuka. Isinya bisa dimuat seluruhnya dengan io.ReadAll:
f, err := os.Open("catatan.txt")
if err != nil {
fmt.Println("gagal membuka berkas:", err)
return
}
defer f.Close()
data, err := io.ReadAll(f)Handle *os.File memegang sumber daya sistem operasi (file descriptor), bukan nilai biasa di memori Go. Handle yang terus terbuka membuat program kehabisan descriptor ketika berkas dibuka berulang. defer f.Close() tepat setelah os.Open sukses menjamin penutupan saat fungsi selesai, apa pun jalur keluarnya.
Membaca Baris demi Baris #
Banyak berkas teks lebih enak diproses per baris daripada sekaligus. bufio.Scanner membaca dari handle, memotong aliran pada tanda baris baru, dan mengembalikan setiap baris sebagai string melalui method Scan():
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Println("gagal memindai:", err)
}Setiap iterasi memproses satu baris tanpa memuat baris lain ke memori. Pola ini menjadi fondasi chapter Menangani File Besar , tempat kita menghitung isi berkas raksasa dengan memori tetap kecil.
Membaca Sebagian dengan Method Read #
Kedua pola sebelumnya menyembunyikan detailnya. Pola keempat menampilkannya: method Read(p []byte) mengisi buffer p maksimal len(p) byte lalu mengembalikan jumlah byte yang benar-benar terisi. Loop berikut membaca berkas 16 byte per panggilan hingga akhir aliran:
buf := make([]byte, 16)
total := 0
for {
n, err := f.Read(buf)
if n > 0 {
total += n
}
if err == io.EOF {
break
}
if err != nil {
fmt.Println("gagal membaca potongan:", err)
break
}
}Nilai io.EOF bukan kegagalan; nilai itu adalah penanda resmi io.Reader bahwa aliran sudah habis, sehingga err == io.EOF adalah cabang normal sebelum pengecekan error sungguhan. Operasi berkas bisa gagal karena sebab luar kendali program — berkas terhapus, disk penuh, izin dicabut — dan pola if err != nil memaksa kita menghadapinya eksplisit.
Ketika Berkas Tidak Ada #
Kasus error paling umum di dunia I/O adalah berkas yang tidak ada. Pemanggilan os.ReadFile("berkas-hilang.txt") menghasilkan error bertipe *fs.PathError dengan teks open berkas-hilang.txt: no such file or directory. Error tersebut membungkus sentinel fs.ErrNotExist, sehingga program bisa mengambil keputusan tanpa mengurai teks pesan:
if _, err := os.ReadFile("berkas-hilang.txt"); err != nil {
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("berkas belum dibuat, pakai nilai bawaan")
}
}Diverifikasi pada Go 1.27: errors.Is mengembalikan true untuk berkas yang hilang; penelusuran rantainya dibahas pada chapter errors.Is dan errors.As
.
Program Utuh #
Berkas contoh.go menggabungkan keempat pola. Program membuat direktori sementara, menulis berkas uji, membacanya dengan keempat pola, lalu mencoba membaca berkas yang sengaja tidak ada; pembersihan otomatis membuat program aman dijalankan berulang:
package main
import (
"bufio"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
)
func main() {
// Direktori kerja sementara agar program aman dijalankan berulang.
dir, err := os.MkdirTemp("", "contoh-baca-*")
if err != nil {
fmt.Println("gagal membuat direktori:", err)
return
}
defer os.RemoveAll(dir)
berkas := filepath.Join(dir, "catatan.txt")
isi := "baris pertama\nbaris kedua\nbaris ketiga\n"
if err := os.WriteFile(berkas, []byte(isi), 0644); err != nil {
fmt.Println("gagal menulis berkas:", err)
return
}
// Pola 1: os.ReadFile memuat seluruh isi berkas ke memori.
data, err := os.ReadFile(berkas)
if err != nil {
fmt.Println("gagal membaca berkas:", err)
return
}
fmt.Printf("os.ReadFile : %d byte\n", len(data))
// Pola 2: os.Open menghasilkan handle, io.ReadAll membaca isinya.
n, err := bacaDenganHandle(berkas)
if err != nil {
fmt.Println("gagal membaca berkas:", err)
return
}
fmt.Printf("io.ReadAll : %d byte\n", n)
// Pola 3: bufio.Scanner membaca baris demi baris.
baris, err := tampilkanBaris(berkas)
if err != nil {
fmt.Println("gagal memindai berkas:", err)
return
}
fmt.Printf("scanner : %d baris\n", baris)
// Pola 4: Read mengisi buffer potongan demi potongan.
total, err := bacaPerPotongan(berkas)
if err != nil {
fmt.Println("gagal membaca potongan:", err)
return
}
fmt.Printf("loop Read : %d byte\n", total)
// Berkas yang tidak ada menghasilkan error nyata dari sistem operasi.
if _, err := os.ReadFile("berkas-hilang.txt"); err != nil {
fmt.Println("error baca :", err)
fmt.Println("ErrNotExist :", errors.Is(err, fs.ErrNotExist))
}
}
// bacaDenganHandle membuka berkas lalu membaca seluruh isinya
// melalui handle yang dijamin tertutup oleh defer.
func bacaDenganHandle(berkas string) (int, error) {
f, err := os.Open(berkas)
if err != nil {
return 0, err
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return 0, err
}
return len(data), nil
}
// tampilkanBaris mencetak setiap baris berkas lalu mengembalikan jumlahnya.
func tampilkanBaris(berkas string) (int, error) {
f, err := os.Open(berkas)
if err != nil {
return 0, err
}
defer f.Close()
baris := 0
scanner := bufio.NewScanner(f)
for scanner.Scan() {
baris++
fmt.Println(" " + scanner.Text())
}
return baris, scanner.Err()
}
// bacaPerPotongan membaca berkas dengan buffer 16 byte per panggilan Read.
func bacaPerPotongan(berkas string) (int, error) {
f, err := os.Open(berkas)
if err != nil {
return 0, err
}
defer f.Close()
buf := make([]byte, 16)
total := 0
for {
n, err := f.Read(buf)
if n > 0 {
total += n
}
if err == io.EOF {
return total, nil
}
if err != nil {
return total, err
}
}
}Jalankan program dari direktori chapter dengan perintah berikut:
go run contoh.goOutput yang Dihasilkan:
os.ReadFile : 39 byte
io.ReadAll : 39 byte
baris pertama
baris kedua
baris ketiga
scanner : 3 baris
loop Read : 39 byte
error baca : open berkas-hilang.txt: no such file or directory
ErrNotExist : trueMari kita telaah baris demi baris program di atas:
- Baris 15-20 (
os.MkdirTemp+defer os.RemoveAll): program membuat direktori sementara acak untuk berkas uji dan menjadwalkan pembersihannya; path acak itu tidak dicetak agar output stabil antar run. - Baris 30-35 (
os.ReadFile): pola pertama memuat 39 byte berkas sekaligus; penutupan berkas ditangani di dalamos.ReadFile. - Baris 70-81 (
bacaDenganHandle): pola kedua membuka handle denganos.Open, mengunci penutupan lewatdefer f.Close()(baris 75), lalu memuat isi denganio.ReadAll— sama dengan pola pertama. - Baris 92-98 (
tampilkanBaris):scanner.Scan()mengembalikantrueselama masih ada baris; tiga baris tercetak satu per satu, error pemindaian diperiksa lewatscanner.Err(). - Baris 109-122 (
bacaPerPotongan): buffer 16 byte diisi berulang lewatf.Read(buf); nilainmencatat byte terbaca per putaran,io.EOFmengakhiri loop dengan total 39 byte. - Baris 62-65 (
os.ReadFilepadaberkas-hilang.txt): berkas yang tidak ada gagal dibuka; teks error asli sistem operasi dicetak apa adanya danerrors.Isterbuktitrue.
Keempat pola ini memberi tangga pilihan: ringkas untuk berkas kecil, terkendali untuk aliran panjang. Persoalan berikutnya adalah arah sebaliknya — menulis berkas dengan aman. Chapter Menulis File
melanjutkan dengan os.WriteFile, os.Create, dan mode append.
Referensi #
- Dokumentasi os: ReadFile — kontrak fungsi baca sekali jalan.
- Dokumentasi os: Open
— pembukaan berkas dan handle
*os.File. - Dokumentasi bufio: Scanner — pemotongan aliran menjadi baris.
- Dokumentasi io: EOF
— penanda resmi akhir aliran pada
io.Reader.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.go
Unduh berkaspackage main
import (
"bufio"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
)
func main() {
// Direktori kerja sementara agar program aman dijalankan berulang.
dir, err := os.MkdirTemp("", "contoh-baca-*")
if err != nil {
fmt.Println("gagal membuat direktori:", err)
return
}
defer os.RemoveAll(dir)
berkas := filepath.Join(dir, "catatan.txt")
isi := "baris pertama\nbaris kedua\nbaris ketiga\n"
if err := os.WriteFile(berkas, []byte(isi), 0644); err != nil {
fmt.Println("gagal menulis berkas:", err)
return
}
// Pola 1: os.ReadFile memuat seluruh isi berkas ke memori.
data, err := os.ReadFile(berkas)
if err != nil {
fmt.Println("gagal membaca berkas:", err)
return
}
fmt.Printf("os.ReadFile : %d byte\n", len(data))
// Pola 2: os.Open menghasilkan handle, io.ReadAll membaca isinya.
n, err := bacaDenganHandle(berkas)
if err != nil {
fmt.Println("gagal membaca berkas:", err)
return
}
fmt.Printf("io.ReadAll : %d byte\n", n)
// Pola 3: bufio.Scanner membaca baris demi baris.
baris, err := tampilkanBaris(berkas)
if err != nil {
fmt.Println("gagal memindai berkas:", err)
return
}
fmt.Printf("scanner : %d baris\n", baris)
// Pola 4: Read mengisi buffer potongan demi potongan.
total, err := bacaPerPotongan(berkas)
if err != nil {
fmt.Println("gagal membaca potongan:", err)
return
}
fmt.Printf("loop Read : %d byte\n", total)
// Berkas yang tidak ada menghasilkan error nyata dari sistem operasi.
if _, err := os.ReadFile("berkas-hilang.txt"); err != nil {
fmt.Println("error baca :", err)
fmt.Println("ErrNotExist :", errors.Is(err, fs.ErrNotExist))
}
}
// bacaDenganHandle membuka berkas lalu membaca seluruh isinya
// melalui handle yang dijamin tertutup oleh defer.
func bacaDenganHandle(berkas string) (int, error) {
f, err := os.Open(berkas)
if err != nil {
return 0, err
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return 0, err
}
return len(data), nil
}
// tampilkanBaris mencetak setiap baris berkas lalu mengembalikan jumlahnya.
func tampilkanBaris(berkas string) (int, error) {
f, err := os.Open(berkas)
if err != nil {
return 0, err
}
defer f.Close()
baris := 0
scanner := bufio.NewScanner(f)
for scanner.Scan() {
baris++
fmt.Println(" " + scanner.Text())
}
return baris, scanner.Err()
}
// bacaPerPotongan membaca berkas dengan buffer 16 byte per panggilan Read.
func bacaPerPotongan(berkas string) (int, error) {
f, err := os.Open(berkas)
if err != nil {
return 0, err
}
defer f.Close()
buf := make([]byte, 16)
total := 0
for {
n, err := f.Read(buf)
if n > 0 {
total += n
}
if err == io.EOF {
return total, nil
}
if err != nil {
return total, err
}
}
}