Setelah program bisa membaca berkas, arah data berikutnya adalah menulis: menyimpan hasil olahan, mencatat log, atau membuat berkas keluaran untuk pengguna. Package os menyediakan empat jalur penulisan dengan karakter berbeda: os.WriteFile untuk menulis sekali jalan, os.Create untuk handle tulis bertahap, os.OpenFile dengan flag append untuk menambah isi, dan bufio.Writer untuk menampung tulisan di buffer. Pada chapter ini kita mempraktikkan keempatnya sambil memperhatikan permission berkas yang terbentuk.
Menulis Sekaligus dengan os.WriteFile #
Fungsi os.WriteFile menerima nama berkas, isi bertipe []byte, dan permission tujuan. Fungsi tersebut membuat berkas (atau memotong yang sudah ada), menulis seluruh isi, lalu menutup berkasnya secara otomatis:
err := os.WriteFile("daftar.txt", []byte("nasi goreng\nmie ayam\n"), 0644)Argumen ketiga 0644 adalah permission: pemilik berkas boleh membaca dan menulis (rw-), sedangkan grup dan pengguna lain hanya boleh membaca (r--r--). Angka oktal ini dibedah tuntas pada chapter File Permissions
. Seperti os.ReadFile, keringkasan pola ini dibayar dengan memuat seluruh isi di memori — cocok untuk keluaran kecil, bukan untuk berkas raksasa.
Membuat Berkas dengan os.Create #
Penulisan bertahap membutuhkan handle. Fungsi os.Create membuka berkas dengan mode tulis, membuat berkas yang belum ada, dan memotong berkas yang sudah ada menjadi kosong:
f, err := os.Create("log.txt")
if err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
defer f.Close()
f.WriteString("baris pertama\n")Method WriteString pada *os.File menulis teks langsung ke berkas, sedangkan method Write menerima []byte. Keduanya mengembalikan jumlah byte yang berhasil ditulis — jumlah yang bisa lebih kecil dari permintaan tanpa disertai error, meski untuk berkas biasa kasus ini nyaris tidak pernah terjadi. Berbeda dengan pola baca pada chapter sebelumnya, penutupan f.Close() di sini lebih dari formalitas: data yang ditulis lewat handle bisa saja masih berada di buffer sistem operasi.
Menambah Isi dengan Flag Append #
os.Create selalu mengosongkan berkas, padahal aplikasi log justru menambah baris di belakang isi lama. Untuk itu os.OpenFile menerima kombinasi flag yang menentukan perilaku pembukaan secara eksplisit:
fa, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)Tiga flag pada contoh di atas punya peran masing-masing:
| Flag | Peran |
|---|---|
os.O_APPEND | Setiap penulisan menempel di akhir isi lama, tidak pernah menimpa |
os.O_CREATE | Berkas dibuat bila belum ada |
os.O_WRONLY | Handle dibuka khusus untuk menulis |
Kombinasi os.O_APPEND|os.O_CREATE|os.O_WRONLY adalah resep baku berkas log: aman untuk berkas baru maupun berkas yang sudah terisi, dan penulisan dari beberapa proses tidak saling menimpa karena setiap penulisan selalu menuju akhir berkas.
Menulis Bertahap dengan bufio.Writer #
Penulisan ke berkas melewati sistem operasi, dan setiap panggilan WriteString langsung menjadi satu operasi tulis di level sistem. Package bufio menyediakan bufio.Writer yang menampung tulisan di buffer memori dulu, lalu mengirimkannya sekaligus:
w := bufio.NewWriter(f)
w.WriteString("satu\n")
w.WriteString("dua\n")
w.Flush()Method Flush wajib dipanggil sebelum berkas ditutup; tulisan yang masih tertinggal di buffer tidak pernah sampai ke disk. Buffer standarnya berukuran 4096 byte dan seluruh mekanismenya dibedah pada chapter Buffered I/O
.
Program Utuh #
Berkas contoh.go mempraktikkan keempat pola pada direktori sementara. Fungsi tampilkan mencetak ukuran dan isi berkas hasil tiap pola tanpa pernah mencetak path sementaranya, sehingga output tetap identik antar run:
package main
import (
"bufio"
"fmt"
"os"
"path/filepath"
)
func main() {
// Direktori kerja sementara agar program aman dijalankan berulang.
dir, err := os.MkdirTemp("", "contoh-tulis-*")
if err != nil {
fmt.Println("gagal membuat direktori:", err)
return
}
defer os.RemoveAll(dir)
// Pola 1: os.WriteFile menulis seluruh isi sekaligus dengan permission 0644.
berkas := filepath.Join(dir, "daftar.txt")
if err := os.WriteFile(berkas, []byte("nasi goreng\nmie ayam\n"), 0644); err != nil {
fmt.Println("gagal menulis berkas:", err)
return
}
tampilkan(berkas, "os.WriteFile")
// Pola 2: os.Create membuat berkas baru atau memotong yang sudah ada.
f, err := os.Create(berkas)
if err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
if _, err := f.WriteString("nasi goreng\nmie ayam\nsate ayam\n"); err != nil {
fmt.Println("gagal menulis:", err)
f.Close()
return
}
if err := f.Close(); err != nil {
fmt.Println("gagal menutup berkas:", err)
return
}
tampilkan(berkas, "os.Create")
// Pola 3: os.OpenFile dengan flag append menambah tanpa memotong isi lama.
fa, err := os.OpenFile(berkas, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("gagal membuka berkas:", err)
return
}
if _, err := fa.WriteString("es teh\n"); err != nil {
fmt.Println("gagal menulis:", err)
fa.Close()
return
}
if err := fa.Close(); err != nil {
fmt.Println("gagal menutup berkas:", err)
return
}
tampilkan(berkas, "append")
// Pola 4: bufio.Writer menampung tulisan di buffer lalu mengirimnya sekaligus.
fw, err := os.Create(filepath.Join(dir, "log.txt"))
if err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
w := bufio.NewWriter(fw)
for _, kata := range []string{"satu", "dua", "tiga"} {
if _, err := w.WriteString(kata + "\n"); err != nil {
fmt.Println("gagal menulis buffer:", err)
fw.Close()
return
}
}
if err := w.Flush(); err != nil {
fmt.Println("gagal mengirim buffer:", err)
fw.Close()
return
}
if err := fw.Close(); err != nil {
fmt.Println("gagal menutup berkas:", err)
return
}
tampilkan(filepath.Join(dir, "log.txt"), "bufio.Writer")
}
// tampilkan mencetak ukuran dan isi berkas tanpa mencetak path-nya.
func tampilkan(berkas, label string) {
data, err := os.ReadFile(berkas)
if err != nil {
fmt.Println("gagal membaca berkas:", err)
return
}
fmt.Printf("%s: %d byte\n", label, len(data))
fmt.Print(string(data))
}Jalankan program dari direktori chapter dengan perintah berikut:
go run contoh.goOutput yang Dihasilkan:
os.WriteFile: 21 byte
nasi goreng
mie ayam
os.Create: 31 byte
nasi goreng
mie ayam
sate ayam
append: 38 byte
nasi goreng
mie ayam
sate ayam
es teh
bufio.Writer: 14 byte
satu
dua
tigaMari kita telaah baris demi baris program di atas:
- Baris 21-25 (
os.WriteFile): pola pertama menulis 21 byte kedaftar.txtdengan permission0644; penutupan berkas ditangani di dalam fungsi. - Baris 28-42 (
os.Create+WriteString):os.Createmembuka berkas yang sama lalu memotongnya — isi lama dua baris hilang, diganti tiga baris baru sepanjang 31 byte;f.Close()diperiksa errornya karena kegagalan penutupan bisa menghilangkan data. - Baris 45-59 (
os.OpenFiledenganos.O_APPEND): flag append menambahkanes tehdi belakang tiga baris lama sehingga ukuran naik menjadi 38 byte tanpa satu baris pun tertimpa. - Baris 62-84 (
bufio.Writer): tigaWriteStringditampung bufferw,Flushmengirim semuanya sekaligus kelog.txtsebelumfw.Close()menutup handle. - Baris 88-96 (
tampilkan): fungsi pembaca membaca isi berkas lalu mencetak label, ukuran, dan isinya — path sementara tidak pernah dicetak, menjaga output tetap stabil antar run.
Keempat pola penulisan kini lengkap: sekali jalan, bertahap, menambah, dan ter-buffer. Pertanyaan berikutnya adalah mengapa buffering terasa begitu berpengaruh pada performa — dan berapa besar sebenarnya penghematannya. Chapter Buffered I/O membuktikan mekanismenya dengan pengamatan langsung pada buffer dan isi disk.
Referensi #
- Dokumentasi os: WriteFile — penulisan sekali jalan beserta aturan permission.
- Dokumentasi os: Create — pembukaan berkas mode tulis dan perilaku pemotongan.
- Dokumentasi os: OpenFile
— daftar flag pembukaan seperti
O_APPENDdanO_CREATE.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.go
Unduh berkaspackage main
import (
"bufio"
"fmt"
"os"
"path/filepath"
)
func main() {
// Direktori kerja sementara agar program aman dijalankan berulang.
dir, err := os.MkdirTemp("", "contoh-tulis-*")
if err != nil {
fmt.Println("gagal membuat direktori:", err)
return
}
defer os.RemoveAll(dir)
// Pola 1: os.WriteFile menulis seluruh isi sekaligus dengan permission 0644.
berkas := filepath.Join(dir, "daftar.txt")
if err := os.WriteFile(berkas, []byte("nasi goreng\nmie ayam\n"), 0644); err != nil {
fmt.Println("gagal menulis berkas:", err)
return
}
tampilkan(berkas, "os.WriteFile")
// Pola 2: os.Create membuat berkas baru atau memotong yang sudah ada.
f, err := os.Create(berkas)
if err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
if _, err := f.WriteString("nasi goreng\nmie ayam\nsate ayam\n"); err != nil {
fmt.Println("gagal menulis:", err)
f.Close()
return
}
if err := f.Close(); err != nil {
fmt.Println("gagal menutup berkas:", err)
return
}
tampilkan(berkas, "os.Create")
// Pola 3: os.OpenFile dengan flag append menambah tanpa memotong isi lama.
fa, err := os.OpenFile(berkas, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("gagal membuka berkas:", err)
return
}
if _, err := fa.WriteString("es teh\n"); err != nil {
fmt.Println("gagal menulis:", err)
fa.Close()
return
}
if err := fa.Close(); err != nil {
fmt.Println("gagal menutup berkas:", err)
return
}
tampilkan(berkas, "append")
// Pola 4: bufio.Writer menampung tulisan di buffer lalu mengirimnya sekaligus.
fw, err := os.Create(filepath.Join(dir, "log.txt"))
if err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
w := bufio.NewWriter(fw)
for _, kata := range []string{"satu", "dua", "tiga"} {
if _, err := w.WriteString(kata + "\n"); err != nil {
fmt.Println("gagal menulis buffer:", err)
fw.Close()
return
}
}
if err := w.Flush(); err != nil {
fmt.Println("gagal mengirim buffer:", err)
fw.Close()
return
}
if err := fw.Close(); err != nil {
fmt.Println("gagal menutup berkas:", err)
return
}
tampilkan(filepath.Join(dir, "log.txt"), "bufio.Writer")
}
// tampilkan mencetak ukuran dan isi berkas tanpa mencetak path-nya.
func tampilkan(berkas, label string) {
data, err := os.ReadFile(berkas)
if err != nil {
fmt.Println("gagal membaca berkas:", err)
return
}
fmt.Printf("%s: %d byte\n", label, len(data))
fmt.Print(string(data))
}