← Kursa Dön
📄 Text · 12 min

Java I/O Temelleri

Programların çoğu bir yerden veri okur, bir yere veri yazar. Dosyadan, ağdan, konsoldan, veritabanından... Java'da bu okuma-yazma işlemlerinin tamamına I/O (Input/Output) denir. Bu derste Java'nın I/O sisteminin temellerini öğreneceğiz.


Akış (Stream) Kavramı — I/O Anlamında

Dikkat: Bu "stream", bir önceki derste gördüğümüz Stream API değil. İsim benzerliği kafanı karıştırmasın.

I/O stream, veri akışını temsil eder. Bir musluk düşün: musluktan su akıyor. Sen suyu bardağa dolduruyorsun (okuma) veya musluğa su pompalıyorsun (yazma). Su bir yönde akar — stream de öyle. Ya okursun, ya yazarsın.

Java'da iki ana stream ailesi var:

AileBirimSınıflarKullanım
Byte Streambyte (8 bit)InputStream / OutputStreamİkili veri: resim, video, PDF
Character Streamchar (16 bit)Reader / WriterMetin verisi: txt, csv, json

Byte Stream — InputStream ve OutputStream

Her şeyin temeli. Resim, müzik, PDF — hepsi byte dizisi. Byte stream bunları olduğu gibi okur/yazar.

InputStream — Byte Okuma

InputStream soyut (abstract) sınıftır. Önemli alt sınıfları:

  • FileInputStream — dosyadan byte okur

  • ByteArrayInputStream — byte dizisinden okur

  • BufferedInputStream — tamponlu okuma

// Dosyadan byte byte okuma
try (FileInputStream fis = new FileInputStream("veri.bin")) {
    int b;
    while ((b = fis.read()) != -1) { // -1 = dosya sonu
        System.out.print(b + " ");
    }
} catch (IOException e) {
    System.out.println("Hata: " + e.getMessage());
}

read() metodu her çağrıda 1 byte okur ve int döner. Dosya bitince -1 döner.

OutputStream — Byte Yazma

// Dosyaya byte yazma
try (FileOutputStream fos = new FileOutputStream("cikti.bin")) {
    byte[] veri = {72, 101, 108, 108, 111}; // "Hello" ASCII
    fos.write(veri);
    fos.flush(); // Tamponu boşalt, diske yaz
} catch (IOException e) {
    System.out.println("Hata: " + e.getMessage());
}

⚠️ `flush()` önemli! Veri hemen diske yazılmaz, önce tamponda (buffer) birikir. flush() tamponu zorla boşaltır. close() çağrılınca otomatik flush yapılır ama açıkça yazmak iyi pratiktir.

Byte Dizisi ile Toplu Okuma

Byte byte okumak yavaştır. Dizi ile toplu okumak çok daha hızlı:

try (FileInputStream fis = new FileInputStream("buyuk_dosya.bin")) {
    byte[] buffer = new byte[4096]; // 4KB buffer
    int okunan;
    
    while ((okunan = fis.read(buffer)) != -1) {
        // buffer'ın 0'dan okunan'a kadar olan kısmı dolu
        System.out.println(okunan + " byte okundu");
    }
} catch (IOException e) {
    e.printStackTrace();
}

read(byte[]) metodu diziye toplu okur ve okunan byte sayısını döner. Bu, performans açısından çok büyük fark yaratır.


Character Stream — Reader ve Writer

Metin dosyaları ile çalışırken byte stream kullanmak zahmetli. Karakter kodlaması (encoding) ile uğraşmak gerekir. Character stream bunu senin yerine yapar.

Reader — Karakter Okuma

// Dosyadan karakter karakter okuma
try (FileReader fr = new FileReader("metin.txt")) {
    int c;
    while ((c = fr.read()) != -1) {
        System.out.print((char) c);
    }
} catch (IOException e) {
    e.printStackTrace();
}

Writer — Karakter Yazma

// Dosyaya metin yazma
try (FileWriter fw = new FileWriter("cikti.txt")) {
    fw.write("Merhaba Dünya!\n");
    fw.write("Java I/O öğreniyoruz.\n");
} catch (IOException e) {
    e.printStackTrace();
}

Encoding Belirtme

FileReader ve FileWriter sistemin varsayılan encoding'ini kullanır. Farklı encoding istiyorsan:

// UTF-8 ile okuma (Java 11+)
try (FileReader fr = new FileReader("metin.txt", StandardCharsets.UTF_8)) {
    // ...
}

// Eski yöntem — InputStreamReader ile wrapping
try (InputStreamReader isr = new InputStreamReader(
        new FileInputStream("metin.txt"), StandardCharsets.UTF_8)) {
    // ...
}

💡 Her zaman encoding belirt! Varsayılan encoding sisteme göre değişir. Windows'ta CP1252, Linux'ta UTF-8 olabilir. Farklı sistemlerde farklı sonuç alırsın. UTF-8 kullan ve açıkça belirt.


Byte Stream vs Character Stream

ÖzellikByte StreamCharacter Stream
Birimbyte (8 bit)char (16 bit)
SınıflarInputStream/OutputStreamReader/Writer
Kullanımİkili dosyalarMetin dosyaları
EncodingManuelOtomatik
Türkçe karakterSorunlu olabilirDoğru çalışır
// YANLIŞ — Türkçe metin için byte stream
try (FileInputStream fis = new FileInputStream("turkce.txt")) {
    int b;
    while ((b = fis.read()) != -1) {
        System.out.print((char) b); // Türkçe karakterler bozuk çıkabilir
    }
}

// DOĞRU — Metin için character stream
try (FileReader fr = new FileReader("turkce.txt", StandardCharsets.UTF_8)) {
    int c;
    while ((c = fr.read()) != -1) {
        System.out.print((char) c); // Türkçe karakterler doğru
    }
}

BufferedReader ve BufferedWriter

Tamponlu (buffered) okuma/yazma, performansı dramatik şekilde artırır. Her seferinde 1 karakter yerine bir blok veri okur/yazar.

Analoji: Markete gidip her seferinde 1 ürün almak yerine, bir alışveriş listesiyle gidip hepsini bir seferde almak.

BufferedReader — Satır Satır Okuma

try (BufferedReader br = new BufferedReader(new FileReader("metin.txt"))) {
    String satir;
    while ((satir = br.readLine()) != null) {
        System.out.println(satir);
    }
} catch (IOException e) {
    e.printStackTrace();
}

readLine() her çağrıda bir satır okur. Dosya bitince null döner. Bu, metin dosyaları için en yaygın okuma pattern'idir.

BufferedWriter — Tamponlu Yazma

try (BufferedWriter bw = new BufferedWriter(new FileWriter("cikti.txt"))) {
    bw.write("Birinci satır");
    bw.newLine(); // Platform bağımsız yeni satır
    bw.write("İkinci satır");
    bw.newLine();
    bw.write("Üçüncü satır");
} catch (IOException e) {
    e.printStackTrace();
}

💡 `bw.newLine()` kullan, `\n` yazma! Windows'ta yeni satır \r\n, Linux/Mac'te \n. newLine() otomatik doğru olanı kullanır.

Performans Farkı

// Yavaş — her read() disk erişimi yapar
FileReader fr = new FileReader("buyuk.txt");

// Hızlı — veriler blok halinde okunur (varsayılan 8KB buffer)
BufferedReader br = new BufferedReader(new FileReader("buyuk.txt"));

// Özel buffer boyutu
BufferedReader br2 = new BufferedReader(new FileReader("buyuk.txt"), 16384); // 16KB

Büyük dosyalarda 10-100x hız farkı olabilir.


try-with-resources — Kaynakları Otomatik Kapat

I/O işlemlerinde kaynakları (stream, reader, writer) mutlaka kapatmalısın. Eskiden finally bloğunda yapılırdı, Java 7'den beri try-with-resources var:

// ESKİ YOL — uzun ve hata riski
BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("dosya.txt"));
    String satir = br.readLine();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) {
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
// YENİ YOL — temiz ve güvenli
try (BufferedReader br = new BufferedReader(new FileReader("dosya.txt"))) {
    String satir = br.readLine();
    System.out.println(satir);
} catch (IOException e) {
    e.printStackTrace();
}
// br otomatik kapatılır — exception olsa bile!

Birden fazla kaynak:

try (
    BufferedReader reader = new BufferedReader(new FileReader("girdi.txt"));
    BufferedWriter writer = new BufferedWriter(new FileWriter("cikti.txt"))
) {
    String satir;
    while ((satir = reader.readLine()) != null) {
        writer.write(satir.toUpperCase());
        writer.newLine();
    }
}

⚠️ try-with-resources yalnızca `AutoCloseable` interface'ini implement eden sınıflarla çalışır. Tüm I/O sınıfları bunu implement eder. Kendi sınıflarında da AutoCloseable implement edebilirsin.


PrintWriter — Kolay Metin Yazma

BufferedWriter güzel ama bazen System.out.println() rahatlığını istersin. PrintWriter tam bunu sunar:

try (PrintWriter pw = new PrintWriter(new FileWriter("rapor.txt"))) {
    pw.println("Rapor Başlığı");
    pw.println("=============");
    pw.printf("Tarih: %s%n", LocalDate.now());
    pw.printf("Toplam: %,d TL%n", 1_250_000);
    pw.println();
    pw.println("İyi günler!");
} catch (IOException e) {
    e.printStackTrace();
}

println(), print(), printf() — tanıdık metodlar, aynen System.out gibi.


Konsol Okuma — Scanner ve BufferedReader

Scanner ile

Scanner scanner = new Scanner(System.in);

System.out.print("İsminiz: ");
String isim = scanner.nextLine();

System.out.print("Yaşınız: ");
int yas = scanner.nextInt();

System.out.println("Merhaba " + isim + ", " + yas + " yaşındasınız.");
scanner.close();

BufferedReader ile (daha hızlı)

try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
    System.out.print("İsminiz: ");
    String isim = br.readLine();
    
    System.out.print("Yaşınız: ");
    int yas = Integer.parseInt(br.readLine());
    
    System.out.println("Merhaba " + isim + ", " + yas + " yaşındasınız.");
} catch (IOException e) {
    e.printStackTrace();
}

Scanner daha kolay ama yavaş. Çok veri okuyacaksan (örn. competitive programming) BufferedReader tercih et.


Decorator Pattern — I/O'nun Tasarım Deseni

Java I/O, Decorator Pattern kullanır. Stream'leri birbirinin içine sararsın:

// Katman katman: FileReader → BufferedReader
BufferedReader br = new BufferedReader(   // 3. Tamponlama ekle
    new InputStreamReader(                 // 2. Byte → char dönüştür
        new FileInputStream("dosya.txt"),  // 1. Dosyadan byte oku
        StandardCharsets.UTF_8             // Encoding belirt
    )
);

Her katman bir özellik ekler:

  1. FileInputStream — dosyadan byte okur

  2. InputStreamReader — byte'ları char'a dönüştürür

  3. BufferedReader — tamponlama ekler, readLine() sağlar

Bu tasarım esnek ama bazen kafa karıştırıcı. Hangi sınıfı hangisinin içine saracağını bilmek deneyimle gelir.


Dosya Kopyalama Örneği

Byte Stream ile (İkili Dosyalar İçin)

public static void kopyala(String kaynak, String hedef) throws IOException {
    try (
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(kaynak));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(hedef))
    ) {
        byte[] buffer = new byte[8192];
        int okunan;
        while ((okunan = bis.read(buffer)) != -1) {
            bos.write(buffer, 0, okunan);
        }
    }
}

Character Stream ile (Metin Dosyaları İçin)

public static void metinKopyala(String kaynak, String hedef) throws IOException {
    try (
        BufferedReader reader = new BufferedReader(
            new FileReader(kaynak, StandardCharsets.UTF_8));
        BufferedWriter writer = new BufferedWriter(
            new FileWriter(hedef, StandardCharsets.UTF_8))
    ) {
        String satir;
        while ((satir = reader.readLine()) != null) {
            writer.write(satir);
            writer.newLine();
        }
    }
}

Yaygın Hatalar

1. Stream'i Kapatmamak

// KÖTÜ — kaynak sızıntısı (resource leak)
FileReader fr = new FileReader("dosya.txt");
// ... işlem yap
// fr.close() unutuldu! Dosya açık kalır.

// İYİ — try-with-resources
try (FileReader fr = new FileReader("dosya.txt")) {
    // ... işlem yap
} // Otomatik kapanır

2. Encoding Belirtmemek

// TEHLİKELİ — sistem encoding'ine bağımlı
new FileReader("dosya.txt");

// GÜVENLİ — encoding açık
new FileReader("dosya.txt", StandardCharsets.UTF_8);

3. Dosya Var mı Kontrol Etmemek

// Patlar — FileNotFoundException
new FileReader("olmayan_dosya.txt");

// Önce kontrol et
File f = new File("dosya.txt");
if (f.exists() && f.isFile()) {
    // Okuma yap
}

I/O Sınıf Hiyerarşisi Özeti

Byte Okuma:
InputStream
├── FileInputStream
├── ByteArrayInputStream
├── BufferedInputStream (decorator)
└── ObjectInputStream

Byte Yazma:
OutputStream
├── FileOutputStream
├── ByteArrayOutputStream
├── BufferedOutputStream (decorator)
└── ObjectOutputStream

Karakter Okuma:
Reader
├── FileReader
├── InputStreamReader (byte→char köprüsü)
├── BufferedReader (decorator)
└── StringReader

Karakter Yazma:
Writer
├── FileWriter
├── OutputStreamWriter (char→byte köprüsü)
├── BufferedWriter (decorator)
├── PrintWriter
└── StringWriter

Özet

  • Java I/O iki aileden oluşur: Byte stream (InputStream/OutputStream) ve Character stream (Reader/Writer)

  • Metin dosyaları için Reader/Writer, ikili dosyalar için InputStream/OutputStream kullan

  • BufferedReader/Writer performansı dramatik artırır — her zaman kullan

  • try-with-resources ile kaynakları otomatik kapat — resource leak'i önle

  • Encoding her zaman belirtStandardCharsets.UTF_8 kullan

  • Java I/O Decorator Pattern kullanır — stream'ler katman katman sarılır