Setelah membaca, sekarang menulis. Kotlin menyediakan extension function File.writeText untuk menulis keseluruhan, File.appendText untuk menambah tanpa menghapus, dan File.bufferedWriter() untuk kontrol penuh. Bab ini juga membahas pola atomic write — strategi menulis file konfigurasi penting tanpa risiko file setengah tertulis jika aplikasi crash di tengah operasi.
Extension Function Utama #
| Function | Perilaku | Cocok untuk |
|---|---|---|
writeText(s) | Overwrite seluruh file | File kecil, konfigurasi |
appendText(s) | Tambah di akhir | Log, append-only |
writeBytes(b) | Overwrite biner | File binary |
bufferedWriter() | Buka BufferedWriter | Penulisan banyak baris |
printWriter() | Buka PrintWriter | Format teks dengan printf |
outputStream() | Buka OutputStream | Kontrol byte-level |
Demo: Beragam Cara Menulis #
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStreamWriter
import java.nio.file.Files
import java.nio.file.StandardCopyOption
const val DEMO_DIR = "/tmp/kotlin-tutorial-tulis-teks"
fun main() {
File(DEMO_DIR).deleteRecursively()
File(DEMO_DIR).mkdirs()
println("=== 1. writeText: tulis seluruh (overwrite) ===")
val fileA = File("$DEMO_DIR/salam.txt")
fileA.writeText("Halo dunia")
println("fileA.length = ${fileA.length()} bytes")
println("isi = ${fileA.readText()}")
println()
println("=== 2. writeText lagi: overwrite isi sebelumnya ===")
fileA.writeText("Halo semuanya") // ganti
println("isi setelah writeText kedua = ${fileA.readText()}")
println()
println("=== 3. appendText: tambah tanpa menghapus ===")
fileA.appendText("\nbaris kedua")
fileA.appendText("\nbaris ketiga")
println("isi setelah dua appendText:")
println(fileA.readText().prependIndent(" "))
println()
println("=== 4. writeText dengan charset ===")
val fileB = File("$DEMO_DIR/karakter.txt")
fileB.writeText("Halo, Siti dari Bandung", Charsets.UTF_8)
println("fileB (UTF-8).isi = ${fileB.readText()}")
println()
println("=== 5. bufferedWriter: tulis banyak baris dengan buffer ===")
val fileC = File("$DEMO_DIR/log.txt")
fileC.bufferedWriter().use { w ->
repeat(3) { i ->
w.write("log entry $i")
w.newLine()
}
}
println("fileC.lines = ${fileC.readLines()}")
println()
println("=== 6. bufferedWriter dengan append (via FileOutputStream) ===")
FileOutputStream(fileC, true).bufferedWriter().use { w ->
w.write("log entry tambahan")
w.newLine()
}
println("isi setelah append:")
println(fileC.readText().prependIndent(" "))
println()
println("=== 7. writeLines: tulis List<String> sekaligus ===")
val fileD = File("$DEMO_DIR/daftar.txt")
val items = listOf("apel", "mangga", "jeruk")
fileD.writeText(items.joinToString("\n"))
println("fileD.lines = ${fileD.readLines()}")
println()
println("=== 8. Pola atomic write: tulis ke file tmp, lalu move ===")
val fileTarget = File("$DEMO_DIR/konfigurasi.txt")
val fileTmp = File("$DEMO_DIR/konfigurasi.txt.tmp")
// Tulis ke file sementara
fileTmp.writeText("versi=2.0\nnama=Aplikasi Demo\n")
// Pindahkan secara atomik
Files.move(fileTmp.toPath(), fileTarget.toPath(),
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE)
println("fileTarget.exists = ${fileTarget.exists()}")
println("fileTmp.exists (harus false) = ${fileTmp.exists()}")
println("fileTarget.isi:")
println(fileTarget.readText().prependIndent(" "))
println()
println("=== 9. PrintWriter: format teks dengan newline otomatis ===")
val fileE = File("$DEMO_DIR/laporan.txt")
fileE.printWriter().use { pw ->
pw.println("Laporan Harian")
pw.println("==============")
pw.printf("Tanggal: %s%n", "2026-01-15")
pw.printf("Total: %d items%n", 42)
}
println("fileE.isi:")
println(fileE.readText().prependIndent(" "))
println()
println("=== 10. OutputStreamWriter: kontrol charset eksplisit ===")
val fileF = File("$DEMO_DIR/output.txt")
FileOutputStream(fileF).use { fos ->
OutputStreamWriter(fos, Charsets.UTF_8).use { w ->
w.write("Halo dengan UTF-8 eksplisit")
}
}
println("fileF.isi = ${fileF.readText()}")
println()
println("=== 11. Bersih-bersih ===")
File(DEMO_DIR).deleteRecursively()
println("demo dir dihapus = ${!File(DEMO_DIR).exists()}")
}
// Extension: OutputStream -> BufferedWriter via OutputStreamWriter
fun java.io.OutputStream.bufferedWriter(): java.io.BufferedWriter =
java.io.BufferedWriter(OutputStreamWriter(this, Charsets.UTF_8))Output yang Dihasilkan:
=== 1. writeText: tulis seluruh (overwrite) ===
fileA.length = 10 bytes
isi = Halo dunia
=== 2. writeText lagi: overwrite isi sebelumnya ===
isi setelah writeText kedua = Halo semuanya
=== 3. appendText: tambah tanpa menghapus ===
isi setelah dua appendText:
Halo semuanya
baris kedua
baris ketiga
=== 4. writeText dengan charset ===
fileB (UTF-8).isi = Halo, Siti dari Bandung
=== 5. bufferedWriter: tulis banyak baris dengan buffer ===
fileC.lines = [log entry 0, log entry 1, log entry 2]
=== 6. bufferedWriter dengan append (via FileOutputStream) ===
isi setelah append:
log entry 0
log entry 1
log entry 2
log entry tambahan
=== 7. writeLines: tulis List<String> sekaligus ===
fileD.lines = [apel, mangga, jeruk]
=== 8. Pola atomic write: tulis ke file tmp, lalu move ===
fileTarget.exists = true
fileTmp.exists (harus false) = false
fileTarget.isi:
versi=2.0
nama=Aplikasi Demo
=== 9. PrintWriter: format teks dengan newline otomatis ===
fileE.isi:
Laporan Harian
==============
Tanggal: 2026-01-15
Total: 42 items
=== 10. OutputStreamWriter: kontrol charset eksplisit ===
fileF.isi = Halo dengan UTF-8 eksplisit
=== 11. Bersih-bersih ===
demo dir dihapus = trueMari kita bedah bagian-bagian penting:
- Bagian 1 (
writeText): extension function yang membukaFileOutputStream(overwrite), menulis teks, lalu menutup.fileA.length = 10 bytes— panjang “Halo dunia” (10 karakter ASCII). - Bagian 2 (overwrite): pemanggilan
writeTextkedua menghapus isi sebelumnya. Output membuktikan “Halo semuanya” menggantikan “Halo dunia”. - Bagian 3 (
appendText): dua pemanggilan menambah dua baris.\ndi awal teks menandakan baris baru — penting karenaappendTexttidak menambah newline otomatis. - Bagian 4 (charset): parameter kedua
Charsets.UTF_8eksplisit. Default sudah UTF-8, tetapi eksplisit membantu saat membaca file dari sumber luar. - Bagian 5 (
bufferedWriter+use { }): idiom untuk menulis banyak baris.w.newLine()menulis newline sesuai sistem operasi (Linux/macOS:\n, Windows:\r\n)..use { }menjamin writer ditutup meski ada exception. - Bagian 6 (append via FileOutputStream): extension
bufferedWriter()Kotlin default ke mode overwrite. Untuk append, bukaFileOutputStream(file, true)dulu, lalu bungkus denganOutputStreamWriterdanBufferedWriter. Extension function kustom di akhir file membungkus pola ini. - Bagian 7 (
joinToString("\n")): trik menulis List<String>ke file dengan newline antar elemen. Tidak ada method bawaanwriteLinesdi Kotlin extension, jadi pola ini adalah idiom standar. - Bagian 8 (atomic write): pola penting untuk konfigurasi. Tulis ke file sementara (
konfigurasi.txt.tmp), lalu pindahkan secara atomik ke target.StandardCopyOption.ATOMIC_MOVEmemastikan operasi move atomic — tidak ada kondisi di mana file target ada tetapi setengah ditulis. Jika aplikasi crash saat menulis file tmp, file target tetap utuh dengan versi lama. - Bagian 9 (
printWriter+printf):PrintWritermendukung format string ala C (%s,%d,%n). Berguna untuk laporan terformat.%nmenulis newline sistem (lebih portabel daripada hardcode\n). - Bagian 10 (OutputStreamWriter eksplisit): pola Java klasik.
FileOutputStreamuntuk byte, dibungkusOutputStreamWriteruntuk karakter dengan charset, laluBufferedWriteruntuk efisiensi. Extension functionFile.writeTextmembungkus pola ini — di sini kita lakukan manual untuk kontrol penuh.
Pola Atomic Write: Mengapa Penting #
Pertimbangkan skenario: aplikasi sedang menulis konfigurasi 2 KB. Daya listrik mati saat 1 KB sudah tertulis. Tanpa atomic write, file konfigurasi sekarang korup — setengah versi lama, setengah versi baru. Aplikasi gagal membaca konfigurasinya sendiri saat startup.
Atomic write memecahkan masalah ini:
1. Tulis ke file.tmp (file.tmp = data baru)
2. Files.move(file.tmp -> file, ATOMIC_MOVE) (file = data baru, file.tmp hilang)Sistem operasi menjamin Files.move dengan ATOMIC_MOVE adalah operasi tak terbagi: ia berhasil penuh atau gagal penuh. Tidak ada kondisi di mana file target kosong atau setengah ditulis.
Pola Append untuk Log File #
Untuk log file yang ingin di-append, gunakan FileOutputStream(file, true) atau File.appendText:
val logFile = File("/var/log/app.log")
if (logFile.length() > MAX_LOG_SIZE) {
logFile.renameTo(File("/var/log/app.log.old"))
}
logFile.appendText("[${waktuTetap}] pesan log\n")Pola di atas melakukan log rotation sederhana — saat file melebihi ukuran tertentu, ganti nama ke .old, lalu mulai file baru. Log rotation produksi biasanya lebih kompleks (gzip file lama, batasi jumlah file lama), tetapi idiom dasarnya sama.
Referensi Lanjutan #
- Kotlin stdlib kotlin.io
— paket
kotlin.ioyang menyediakanFile.writeText,appendText, danwriteLines - Kotlin stdlib java.io.file
— alias Kotlin stdlib untuk
java.io.Filedengan extension menulis teks
Selanjutnya #
Bab berikutnya, Buffered File Operations, akan membahas operasi berbuffer untuk file besar: BufferedReader, BufferedWriter, forEachLine, dan strategi memproses file log berukuran gigabyte tanpa memuat seluruhnya ke memori.