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:
| Aile | Birim | Sınıflar | Kullanım |
|---|---|---|---|
| Byte Stream | byte (8 bit) | InputStream / OutputStream | İkili veri: resim, video, PDF |
| Character Stream | char (16 bit) | Reader / Writer | Metin 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 okurByteArrayInputStream— byte dizisinden okurBufferedInputStream— 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
| Özellik | Byte Stream | Character Stream |
|---|---|---|
| Birim | byte (8 bit) | char (16 bit) |
| Sınıflar | InputStream/OutputStream | Reader/Writer |
| Kullanım | İkili dosyalar | Metin dosyaları |
| Encoding | Manuel | Otomatik |
| Türkçe karakter | Sorunlu olabilir | Doğ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); // 16KBBü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
AutoCloseableimplement 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:
FileInputStream— dosyadan byte okurInputStreamReader— byte'ları char'a dönüştürürBufferedReader— 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ır2. 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 belirt —
StandardCharsets.UTF_8kullanJava I/O Decorator Pattern kullanır — stream'ler katman katman sarılır
AI Asistan
Sorularını yanıtlamaya hazır