IO

IO #

Input dan Output adalah operasi yang ada di hampir semua aplikasi — membaca file konfigurasi, menulis log, memproses CSV, atau menyimpan hasil komputasi. Java menyediakan dua keluarga API untuk I/O: java.io yang berbasis stream dan sudah ada sejak Java 1, dan java.nio (New I/O) yang diperkenalkan di Java 4 dan diperbarui besar-besaran di Java 7 dengan NIO.2. Untuk hampir semua kebutuhan file modern, NIO.2 melalui java.nio.file.Files dan java.nio.file.Path adalah pilihan yang tepat — lebih ekspresif, lebih aman, dan dilengkapi method utility yang menyelesaikan pekerjaan umum dalam satu baris. API java.io masih relevan untuk streaming dan chaining decorator, tapi untuk manipulasi file dan path, NIO.2 jauh lebih baik. Artikel ini membahas keduanya secara mendalam, dengan penekanan pada pola yang benar dan anti-pattern yang sering menyebabkan bug dan resource leak.

Dua Keluarga API I/O #

Sebelum menulis kode, pahami kapan menggunakan masing-masing:

java.io
├── InputStream / OutputStream     ← byte stream (binary)
├── Reader / Writer                 ← character stream (teks)
├── FileInputStream/FileOutputStream← akses file level rendah
├── BufferedReader/BufferedWriter   ← buffering untuk performa
└── File                           ← representasi path (legacy)

java.nio.file (NIO.2 — Java 7+)
├── Path                           ← representasi path (modern)
├── Paths / Path.of()              ← factory untuk membuat Path
├── Files                          ← utility operasi file (satu baris!)
└── FileSystem / FileSystems       ← abstraksi filesystem
flowchart TD
    TASK{Kebutuhan}

    TASK -->|Baca/tulis file teks\natau binary sederhana| NIO2[NIO.2 — Files + Path\nPilihan utama]
    TASK -->|Stream processing\ndecorator pattern| JAVAIO[java.io\nInputStream/Reader]
    TASK -->|Non-blocking I/O\nribuan koneksi| NIO[java.nio\nChannel + Selector]
    TASK -->|Operasi path\nmanipulasi direktori| NIO2

    NIO2 -->|jika butuh streaming| JAVAIO

Path — Representasi Lokasi File #

Path adalah cara modern Java merepresentasikan lokasi di filesystem — menggantikan java.io.File yang lama.

import java.nio.file.Path;
import java.nio.file.Paths;

// Membuat Path (Java 11+: Path.of() lebih idiomatis dari Paths.get())
Path p1 = Path.of("/home/user/dokumen/laporan.txt");       // absolute
Path p2 = Path.of("data", "input", "berkas.csv");          // relative, cross-platform
Path p3 = Paths.get("/tmp/cache");                          // cara lama, masih valid

// Navigasi path
Path dir = Path.of("/home/user/proyek");
Path file = dir.resolve("src/Main.java");                   // gabungkan path
// "/home/user/proyek/src/Main.java"

Path parent = file.getParent();                             // "/home/user/proyek/src"
Path namaFile = file.getFileName();                         // "Main.java"
Path root = file.getRoot();                                 // "/" (Unix) atau "C:\" (Windows)

// Informasi path
int dalamanan = file.getNameCount();                        // jumlah segmen
Path segmen = file.getName(2);                              // segmen ke-2 (0-based)

// Normalisasi dan absolutisasi
Path kotor = Path.of("/home/user/../user/./dokumen");
Path bersih = kotor.normalize();                            // "/home/user/dokumen"
Path absolut = Path.of("relatif/path").toAbsolutePath();   // dari current working dir

// Relativize — hitung path relatif antara dua path
Path basis = Path.of("/home/user");
Path tujuan = Path.of("/home/user/dokumen/laporan.txt");
Path relatif = basis.relativize(tujuan);                    // "dokumen/laporan.txt"

// Komparasi
boolean sama = p1.equals(p2);                               // bandingkan nilai path
boolean dimulaiDengan = file.startsWith(dir);               // true

// Konversi ke URI dan File (untuk interop dengan API lama)
java.net.URI uri = file.toUri();                            // "file:///home/user/..."
java.io.File fileObj = file.toFile();                       // interop dengan java.io

// Cross-platform — Path.of() otomatis gunakan separator yang benar
Path crossPlatform = Path.of("data", "output", "hasil.csv");
// Windows: "data\output\hasil.csv"
// Unix:    "data/output/hasil.csv"

Files — Operasi File Satu Baris #

Files adalah utility class berisi method statik untuk semua operasi file umum. Ini adalah API yang harus kamu gunakan untuk sebagian besar kebutuhan file.

Baca dan Tulis File Teks #

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class OperasiFileTeks {

    public void demo() throws Exception {
        Path filePath = Path.of("data/laporan.txt");

        // ===== MEMBACA =====

        // Baca seluruh isi sebagai String — untuk file kecil
        String isi = Files.readString(filePath);                        // Java 11+
        String isiCharset = Files.readString(filePath, StandardCharsets.UTF_8);

        // Baca semua baris sebagai List<String>
        List<String> baris = Files.readAllLines(filePath);              // UTF-8 default
        List<String> barisLatin = Files.readAllLines(filePath,
            StandardCharsets.ISO_8859_1);

        // Baca sebagai Stream<String> — untuk file besar (lazy, tidak load semua ke memory)
        try (var stream = Files.lines(filePath)) {
            stream
                .filter(b -> !b.isBlank())
                .map(String::trim)
                .forEach(System.out::println);
        } // stream ditutup otomatis

        // Baca sebagai byte array — untuk file binary kecil
        byte[] bytes = Files.readAllBytes(filePath);

        // ===== MENULIS =====

        // Tulis String ke file (buat baru atau timpa yang ada)
        Files.writeString(filePath, "Konten baru");                     // Java 11+
        Files.writeString(filePath, "Konten UTF-8", StandardCharsets.UTF_8);

        // Tulis dengan opsi tambahan
        Files.writeString(filePath, "Baris tambahan\n",
            StandardOpenOption.APPEND);         // tambah ke akhir file
        Files.writeString(filePath, "Hanya jika belum ada",
            StandardOpenOption.CREATE_NEW);     // gagal jika file sudah ada

        // Tulis List<String> sebagai baris
        List<String> konten = List.of("baris 1", "baris 2", "baris 3");
        Files.write(filePath, konten);
        Files.write(filePath, konten, StandardCharsets.UTF_8,
            StandardOpenOption.APPEND);

        // Tulis byte array
        byte[] data = "data binary".getBytes(StandardCharsets.UTF_8);
        Files.write(filePath, data);
    }
}

Operasi File dan Direktori #

public class ManajemenFile {

    public void demo() throws Exception {
        Path src = Path.of("sumber.txt");
        Path dst = Path.of("tujuan.txt");
        Path dir = Path.of("direktori/baru");

        // ===== CEK KEBERADAAN DAN ATRIBUT =====
        boolean ada = Files.exists(src);
        boolean tidakAda = Files.notExists(src);
        boolean adaFile = Files.isRegularFile(src);
        boolean adaDir = Files.isDirectory(dir);
        boolean bisaDibaca = Files.isReadable(src);
        boolean bisaDitulis = Files.isWritable(src);
        boolean bisaDieksekusi = Files.isExecutable(src);
        long ukuran = Files.size(src);                          // dalam bytes
        java.time.Instant diubah = Files.getLastModifiedTime(src).toInstant();

        // ===== COPY DAN MOVE =====

        // Copy file
        Files.copy(src, dst);                                   // gagal jika dst sudah ada
        Files.copy(src, dst,
            java.nio.file.StandardCopyOption.REPLACE_EXISTING, // timpa jika ada
            java.nio.file.StandardCopyOption.COPY_ATTRIBUTES); // salin atribut file

        // Move / rename file
        Files.move(src, dst);
        Files.move(src, dst,
            java.nio.file.StandardCopyOption.REPLACE_EXISTING,
            java.nio.file.StandardCopyOption.ATOMIC_MOVE);      // atomic pada filesystem yang mendukung

        // ===== HAPUS =====
        Files.delete(src);              // throw exception jika tidak ada
        Files.deleteIfExists(src);      // tidak throw jika tidak ada

        // ===== DIREKTORI =====
        Files.createDirectory(dir);             // buat satu level direktori
        Files.createDirectories(dir);           // buat semua level (mkdir -p)

        // File sementara
        Path tmpFile = Files.createTempFile("prefix-", ".tmp");
        Path tmpDir = Files.createTempDirectory("tmpdir-");
        // INGAT: hapus file sementara setelah selesai
        tmpFile.toFile().deleteOnExit(); // hapus saat JVM keluar
    }
}

Membaca File dengan BufferedReader #

Untuk file teks berukuran besar yang perlu diproses baris per baris, BufferedReader lebih efisien dari Files.readAllLines() karena tidak memuat seluruh file ke memori sekaligus.

import java.io.*;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;

public class BufferedReaderDemo {

    // ✗ ANTI-PATTERN: FileReader tanpa charset — bergantung platform default
    public void bacaTanpaCharset(String path) throws IOException {
        try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
            String baris;
            while ((baris = reader.readLine()) != null) {
                System.out.println(baris);
            }
        }
    }

    // ✓ BENAR: selalu tentukan charset eksplisit
    public void bacaDenganCharset(Path path) throws IOException {
        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            String baris;
            while ((baris = reader.readLine()) != null) {
                // proses baris
            }
        }
        // try-with-resources otomatis menutup reader meski terjadi exception
    }

    // ✓ LEBIH MODERN: Files.lines() dengan Stream API
    public long hitungBarisNonKosong(Path path) throws IOException {
        try (var lines = Files.lines(path, StandardCharsets.UTF_8)) {
            return lines.filter(b -> !b.isBlank()).count();
        }
    }

    // Proses file CSV besar tanpa muat semua ke memory
    public void prosesCSVBesar(Path csvPath) throws IOException {
        try (var lines = Files.lines(csvPath, StandardCharsets.UTF_8)) {
            lines
                .skip(1)                          // lewati header
                .map(baris -> baris.split(","))   // pecah kolom
                .filter(kolom -> kolom.length >= 3)
                .forEach(kolom -> {
                    String nama = kolom[0].trim();
                    String harga = kolom[1].trim();
                    System.out.println("Produk: " + nama + " | Harga: " + harga);
                });
        }
    }
}

import java.io.*;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;

public class BufferedWriterDemo {

    // ✓ BENAR: BufferedWriter dengan charset eksplisit
    public void tulisDenganBuffer(Path path, List<String> data) throws IOException {
        try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
            for (String baris : data) {
                writer.write(baris);
                writer.newLine(); // \n atau \r\n sesuai OS
            }
        }
    }

    // Append ke file yang sudah ada
    public void tambahKanLog(Path logPath, String pesan) throws IOException {
        try (BufferedWriter writer = Files.newBufferedWriter(
                logPath,
                StandardCharsets.UTF_8,
                StandardOpenOption.CREATE,  // buat jika belum ada
                StandardOpenOption.APPEND)) // tambah ke akhir
        {
            writer.write(java.time.LocalDateTime.now() + " | " + pesan);
            writer.newLine();
        }
    }

    // PrintWriter — lebih nyaman untuk output terformat
    public void tulisLaporan(Path path) throws IOException {
        try (PrintWriter pw = new PrintWriter(
                Files.newBufferedWriter(path, StandardCharsets.UTF_8))) {
            pw.println("=== Laporan Penjualan ===");
            pw.printf("Tanggal: %tF%n", java.time.LocalDate.now());
            pw.printf("Total: Rp %.2f%n", 15000000.0);
            pw.flush(); // pastikan semua data ditulis
        }
    }
}

Membaca dan Menulis File Binary #

Untuk file binary (gambar, PDF, arsip, data serialisasi), gunakan stream byte tanpa karakter encoding.

import java.io.*;
import java.nio.file.*;

public class FileBinaryDemo {

    // Baca file binary (untuk file kecil — muat semua ke memory)
    public byte[] bacaFileBinary(Path path) throws IOException {
        return Files.readAllBytes(path); // semua byte sekaligus
    }

    // Baca file binary besar dengan buffer
    public void prosesFileBesar(Path src, Path dst) throws IOException {
        try (InputStream in = new BufferedInputStream(Files.newInputStream(src));
             OutputStream out = new BufferedOutputStream(Files.newOutputStream(dst))) {

            byte[] buffer = new byte[8192]; // 8 KB buffer
            int jumlahBaca;
            while ((jumlahBaca = in.read(buffer)) != -1) {
                out.write(buffer, 0, jumlahBaca);
            }
        }
        // Keduanya ditutup otomatis — bahkan jika terjadi exception di tengah proses
    }

    // Transfer file — cara paling efisien menggunakan OS-level copy
    public void salinFile(Path src, Path dst) throws IOException {
        try (InputStream in = Files.newInputStream(src);
             OutputStream out = Files.newOutputStream(dst)) {
            in.transferTo(out); // Java 9+ — delegasikan ke OS untuk efisiensi maksimal
        }
    }

    // DataInputStream / DataOutputStream — baca/tulis tipe primitif ke binary stream
    public void tulisDataBinary(Path path) throws IOException {
        try (DataOutputStream dos = new DataOutputStream(
                new BufferedOutputStream(Files.newOutputStream(path)))) {
            dos.writeInt(42);
            dos.writeDouble(3.14);
            dos.writeBoolean(true);
            dos.writeUTF("halo");         // tulis String sebagai UTF-8 dengan length prefix
        }
    }

    public void bacaDataBinary(Path path) throws IOException {
        try (DataInputStream dis = new DataInputStream(
                new BufferedInputStream(Files.newInputStream(path)))) {
            int angka = dis.readInt();    // harus dibaca dalam urutan yang sama dengan penulisan!
            double desimal = dis.readDouble();
            boolean bool = dis.readBoolean();
            String teks = dis.readUTF();

            System.out.printf("int=%d, double=%.2f, bool=%b, str=%s%n",
                angka, desimal, bool, teks);
        }
    }
}

Traversal Direktori #

Daftar Isi Direktori #

import java.nio.file.*;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

public class TraversalDirektori {

    // Daftar isi direktori (satu level saja)
    public List<Path> daftarIsi(Path dir) throws IOException {
        try (var stream = Files.list(dir)) {
            return stream
                .sorted()       // urutkan berdasarkan nama
                .toList();
        }
    }

    // Filter hanya file tertentu
    public List<Path> cariFileJava(Path dir) throws IOException {
        try (var stream = Files.list(dir)) {
            return stream
                .filter(Files::isRegularFile)
                .filter(p -> p.toString().endsWith(".java"))
                .sorted()
                .toList();
        }
    }

    // Traversal rekursif seluruh pohon direktori
    public List<Path> semuaFile(Path root) throws IOException {
        try (var stream = Files.walk(root)) {
            return stream
                .filter(Files::isRegularFile)
                .sorted()
                .toList();
        }
    }

    // walk dengan kedalaman maksimum
    public List<Path> fileKedalaman2(Path root) throws IOException {
        try (var stream = Files.walk(root, 2)) { // maks 2 level ke bawah
            return stream
                .filter(Files::isRegularFile)
                .toList();
        }
    }

    // Hitung total ukuran direktori
    public long totalUkuranDir(Path dir) throws IOException {
        try (var stream = Files.walk(dir)) {
            return stream
                .filter(Files::isRegularFile)
                .mapToLong(p -> {
                    try { return Files.size(p); }
                    catch (IOException e) { return 0L; }
                })
                .sum();
        }
    }

    // find() — traversal dengan filter yang lebih kuat
    public List<Path> cariFileBesar(Path root, long ukuranMin) throws IOException {
        try (var stream = Files.find(root, Integer.MAX_VALUE,
                (path, attrs) ->
                    attrs.isRegularFile() &&
                    attrs.size() > ukuranMin)) {
            return stream.toList();
        }
    }

    // Hapus direktori beserta isinya (rekursif)
    public void hapusDirektori(Path dir) throws IOException {
        // Files.delete() gagal jika direktori tidak kosong
        // Harus hapus isi dulu menggunakan walk dalam urutan terbalik
        try (var stream = Files.walk(dir)) {
            stream.sorted(java.util.Comparator.reverseOrder())
                .forEach(path -> {
                    try { Files.delete(path); }
                    catch (IOException e) {
                        throw new RuntimeException("Gagal hapus: " + path, e);
                    }
                });
        }
    }

    // Salin direktori beserta isinya
    public void salinDirektori(Path src, Path dst) throws IOException {
        try (var stream = Files.walk(src)) {
            stream.forEach(sumberPath -> {
                Path tujuanPath = dst.resolve(src.relativize(sumberPath));
                try {
                    if (Files.isDirectory(sumberPath)) {
                        Files.createDirectories(tujuanPath);
                    } else {
                        Files.copy(sumberPath, tujuanPath,
                            StandardCopyOption.REPLACE_EXISTING);
                    }
                } catch (IOException e) {
                    throw new RuntimeException("Gagal salin: " + sumberPath, e);
                }
            });
        }
    }
}

File Attributes dan Metadata #

import java.nio.file.*;
import java.nio.file.attribute.*;
import java.time.Instant;

public class AtributFileDemo {

    public void bacaAtribut(Path path) throws Exception {
        // Atribut dasar
        BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);

        System.out.println("Regular file: " + attrs.isRegularFile());
        System.out.println("Directory: " + attrs.isDirectory());
        System.out.println("Symbolic link: " + attrs.isSymbolicLink());
        System.out.println("Ukuran: " + attrs.size() + " bytes");
        System.out.println("Dibuat: " + attrs.creationTime().toInstant());
        System.out.println("Diubah terakhir: " + attrs.lastModifiedTime().toInstant());
        System.out.println("Diakses terakhir: " + attrs.lastAccessTime().toInstant());

        // Ubah waktu modifikasi
        Files.setLastModifiedTime(path,
            FileTime.from(Instant.now()));

        // POSIX atribut (Unix/Linux/Mac)
        if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
            PosixFileAttributes posix = Files.readAttributes(path, PosixFileAttributes.class);
            System.out.println("Owner: " + posix.owner().getName());
            System.out.println("Group: " + posix.group().getName());
            System.out.println("Permissions: " + PosixFilePermissions.toString(posix.permissions()));

            // Ubah permission (setara chmod 644)
            Files.setPosixFilePermissions(path, PosixFilePermissions.fromString("rw-r--r--"));
        }
    }
}

WatchService — Pantau Perubahan File #

WatchService memungkinkan aplikasi bereaksi terhadap perubahan pada direktori — file baru ditambahkan, diubah, atau dihapus — tanpa perlu polling berulang.

import java.nio.file.*;

public class FileWatcherDemo {

    public void pantauDirektori(Path dir) throws Exception {
        WatchService watcher = FileSystems.getDefault().newWatchService();

        // Daftar event yang ingin dipantau
        dir.register(watcher,
            StandardWatchEventKinds.ENTRY_CREATE,  // file baru dibuat
            StandardWatchEventKinds.ENTRY_MODIFY,  // file diubah
            StandardWatchEventKinds.ENTRY_DELETE   // file dihapus
        );

        System.out.println("Memantau direktori: " + dir);

        while (true) {
            // take() — blokir sampai ada event
            WatchKey key = watcher.take();

            for (WatchEvent<?> event : key.pollEvents()) {
                WatchEvent.Kind<?> jenis = event.kind();

                // OVERFLOW berarti beberapa event mungkin terlewat
                if (jenis == StandardWatchEventKinds.OVERFLOW) {
                    System.out.println("Beberapa event mungkin terlewat!");
                    continue;
                }

                // Nama file yang berubah
                @SuppressWarnings("unchecked")
                WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
                Path namaFile = pathEvent.context();
                Path fullPath = dir.resolve(namaFile);

                if (jenis == StandardWatchEventKinds.ENTRY_CREATE) {
                    System.out.println("File dibuat: " + fullPath);
                } else if (jenis == StandardWatchEventKinds.ENTRY_MODIFY) {
                    System.out.println("File diubah: " + fullPath);
                } else if (jenis == StandardWatchEventKinds.ENTRY_DELETE) {
                    System.out.println("File dihapus: " + fullPath);
                }
            }

            // Reset key — wajib agar bisa menerima event berikutnya
            boolean valid = key.reset();
            if (!valid) {
                System.out.println("Direktori tidak lagi dapat dipantau");
                break;
            }
        }
    }
}

Serialisasi Objek #

Java menyediakan mekanisme serialisasi bawaan untuk mengkonversi objek ke byte stream dan sebaliknya. Perlu diperhatikan bahwa serialisasi Java bawaan punya banyak kelemahan dan sebaiknya diganti dengan format yang lebih modern (JSON, Protobuf, Avro) untuk komunikasi antar sistem.

import java.io.*;
import java.nio.file.*;

// Kelas yang bisa diserialisasi harus implements Serializable
public class Produk implements Serializable {

    // serialVersionUID penting! Jika tidak ada dan struktur kelas berubah,
    // deserialisasi objek lama akan gagal dengan InvalidClassException
    @Serial
    private static final long serialVersionUID = 1L;

    private String nama;
    private double harga;

    // Field yang tidak perlu diserialisasi — ditandai transient
    private transient String cacheKey; // tidak akan diserialisasi

    public Produk(String nama, double harga) {
        this.nama = nama;
        this.harga = harga;
    }

    // getter...
}

public class SerializasiDemo {

    // Serialisasi — objek ke file
    public void simpan(Produk produk, Path path) throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new BufferedOutputStream(Files.newOutputStream(path)))) {
            oos.writeObject(produk);
        }
    }

    // Deserialisasi — file ke objek
    public Produk muat(Path path) throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(
                new BufferedInputStream(Files.newInputStream(path)))) {
            return (Produk) ois.readObject();
        }
    }
}
Serialisasi Java bawaan (ObjectInputStream/ObjectOutputStream) memiliki risiko keamanan serius — deserialisasi data yang tidak terpercaya dapat menyebabkan Remote Code Execution. Untuk pertukaran data antar sistem atau penyimpanan jangka panjang, gunakan JSON (Jackson/Gson), Protobuf, atau format yang lebih modern. Serialisasi Java bawaan hanya aman jika data berasal dari sumber yang sepenuhnya terpercaya.

Pola Penanganan Resource yang Aman #

Try-with-Resources #

Selalu gunakan try-with-resources untuk memastikan resource ditutup meski terjadi exception:

// ✗ ANTI-PATTERN: tutup manual — resource leak jika exception terjadi sebelum close()
BufferedReader reader = null;
try {
    reader = Files.newBufferedReader(path);
    String baris = reader.readLine();
    // jika exception di sini, reader.close() tidak pernah dipanggil!
} finally {
    if (reader != null) {
        reader.close(); // boilerplate dan rawan lupa
    }
}

// ✓ BENAR: try-with-resources — close() dipanggil otomatis, bahkan saat exception
try (BufferedReader reader = Files.newBufferedReader(path)) {
    String baris = reader.readLine();
    // exception di sini? reader.close() tetap dipanggil!
}

// Multiple resources — ditutup dalam urutan terbalik (ois ditutup sebelum bis)
try (InputStream bis = new BufferedInputStream(Files.newInputStream(src));
     ObjectInputStream ois = new ObjectInputStream(bis)) {
    Object obj = ois.readObject();
}

// Resource kustom — implementasikan AutoCloseable
public class KoneksiDB implements AutoCloseable {
    public KoneksiDB(String url) { /* buka koneksi */ }

    @Override
    public void close() {
        /* tutup koneksi — dipanggil otomatis oleh try-with-resources */
        System.out.println("Koneksi DB ditutup");
    }
}

try (KoneksiDB db = new KoneksiDB("jdbc:postgresql://...")) {
    // gunakan db
} // db.close() dipanggil otomatis

Kapan Menggunakan API yang Mana #

GUNAKAN Files (NIO.2) JIKA:
  ✓ Baca/tulis file teks — Files.readString(), Files.writeString()
  ✓ Baca/tulis binary sederhana — Files.readAllBytes(), Files.write()
  ✓ Manipulasi file/direktori — copy, move, delete, createDirectories
  ✓ Traversal direktori — Files.list(), Files.walk(), Files.find()
  ✓ Cek atribut dan metadata file
  ✓ Hampir semua kebutuhan file modern

GUNAKAN java.io Stream JIKA:
  ✓ File sangat besar yang tidak muat di memory — streaming line by line
  ✓ Chaining decorator (Buffer + Gzip + Cipher + stream)
  ✓ Serialisasi objek Java
  ✓ Interoperabilitas dengan API lama yang menggunakan InputStream/Reader

GUNAKAN WatchService JIKA:
  ✓ Aplikasi perlu bereaksi terhadap perubahan file secara real-time
  ✓ Hot reload konfigurasi tanpa restart aplikasi
  ✓ Pipeline yang diproses ketika file baru masuk ke direktori

Ringkasan #

  • Files dan Path (NIO.2) adalah API modern yang harus digunakan untuk hampir semua operasi file — Files.readString(), Files.writeString(), Files.copy(), Files.walk() menyelesaikan pekerjaan umum dalam satu baris yang ekspresif.
  • Selalu gunakan try-with-resources untuk semua resource I/O — BufferedReader, InputStream, Stream<Path> semuanya AutoCloseable dan harus ditutup untuk mencegah file handle leak.
  • Tentukan charset secara eksplisit (StandardCharsets.UTF_8) setiap kali bekerja dengan teks. Mengandalkan default platform menyebabkan bug yang sulit direproduksi di lingkungan berbeda.
  • Files.lines() untuk file teks besar — mengembalikan Stream<String> yang lazy, tidak memuat seluruh file ke memori seperti Files.readAllLines().
  • BufferedInputStream/BufferedOutputStream selalu wrap InputStream/OutputStream mentah untuk performa — operasi I/O tanpa buffer sangat lambat karena setiap read()/write() menjadi system call.
  • Path.of() (Java 11+) menggantikan Paths.get() dan new File() — gunakan ini untuk semua representasi path baru. Cross-platform otomatis menggunakan separator yang benar.
  • in.transferTo(out) (Java 9+) adalah cara paling efisien menyalin stream — delegasikan ke OS-level untuk menghindari copy buffer di Java.
  • Hindari serialisasi Java bawaan untuk data yang dikomunikasikan antar sistem atau disimpan jangka panjang — gunakan JSON, Protobuf, atau Avro yang lebih aman, portabel, dan mudah di-evolve.

← Sebelumnya: Strings   Berikutnya: Math →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact