Yazılım geliştirme süreçlerinde harici verilerle çalışmak en temel gereksinimlerden biridir. Kullanıcı ayarlarının saklanması, log kayıtlarının tutulması veya büyük veri setlerinin işlenmesi gibi birçok senaryoda dosya sistemine erişim zorunludur. Java platformu, ilk sürümlerinden bu yana dosya işlemleri için kapsamlı kütüphaneler sunmaktadır. Bu rehberde, Java dosya okuma ve yazma tekniklerini temelden ele alacak, hem geleneksel I/O hem de modern NIO.2 yaklaşımlarını inceleyeceğiz.
Java I/O ve NIO Mimarisine Genel Bakış
Java’da girdi ve çıktı (I/O) işlemleri temelde iki ana kategoriye ayrılır:
- Byte tabanlı akışlar: Resim, ses, video veya derlenmiş dosyalar gibi ikili (binary) verileri işlemek için
InputStreamveOutputStreamsınıfları kullanılır. - Karakter tabanlı akışlar: Metin tabanlı verileri işlemek için
ReaderveWritersınıfları tercih edilir. Bu sınıflar karakter kodlamalarını (UTF-8, ASCII gibi) otomatik olarak yönetir.
Java 7 ile birlikte gelen NIO.2 (New I/O) paketi (java.nio.file), dosya sistemi işlemlerini çok daha performanslı, esnek ve okunabilir bir yapıya kavuşturmuştur. Güncel projelerde dosya işlemlerini yaparken hem geleneksel java.io paketindeki akışları hem de java.nio.file.Files sınıfını amaca uygun şekilde kullanmak gerekir.
Java Dosya Okuma Yöntemleri
Dosya okurken dosyanın boyutu, yapısı ve uygulamanın bellek kısıtlamaları belirleyici rol oynar. Java dosya okuma amacıyla en sık başvurulan yöntemleri inceleyelim.
BufferedReader ile Satır Satır Okuma
Büyük boyutlu metin dosyalarında tüm içeriği bir anda belleğe yüklemek OutOfMemoryError hatasına yol açabilir. BufferedReader, verileri bellek tamponuna (buffer) alarak satır satır okur ve sistem çağrısı maliyetini minimuma indirir.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class DosyaOkumaBuffer {
public static void main(String[] args) {
String dosyaYolu = "ornek.txt";
try (BufferedReader br = new BufferedReader(new FileReader(dosyaYolu))) {
String satir;
while ((satir = br.readLine()) != null) {
System.out.println(satir);
}
} catch (IOException e) {
System.err.println("Dosya okunurken bir hata oluştu: " + e.getMessage());
}
}
}
Bu örnekte FileReader dosya karakter akışını açarken, BufferedReader bu akışı tamponlar. readLine() metodu dosya sonuna gelene kadar her satırı döngü içinde string olarak döndürür. try-with-resources bloğu sayesinde dosya işlemi bittiğinde veya bir hata meydana geldiğinde akış otomatik olarak kapatılır.
Scanner Sınıfı ile Okuma
Scanner sınıfı, özellikle ayrıştırılması (parsing) gereken verilerde kullanışlıdır. Sayısal değerler veya belirli ayraçlarla (delimiter) ayrılmış veriler okunurken tercih edilir.
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class DosyaOkumaScanner {
public static void main(String[] args) {
File dosya = new File("veriler.txt");
try (Scanner scanner = new Scanner(dosya)) {
while (scanner.hasNextLine()) {
String satir = scanner.nextLine();
System.out.println(satir);
}
} catch (FileNotFoundException e) {
System.err.println("Dosya bulunamadı: " + e.getMessage());
}
}
}
Bu kod bloğunda Scanner, dosyayı satır satır denetler. Metin okuma performansı BufferedReader kadar yüksek olmasa da nextInt(), nextDouble() gibi metodlarıyla tip dönüşümlerini doğrudan yapabilme esnekliği sunar.
Files Sınıfı ile Modern Okuma (NIO.2)
Küçük ve orta ölçekli dosyalar için Java 7 ve 8 ile gelen Files sınıfı en temiz sözdizimini sağlar. Tek satırda tüm dosya içeriğini bir listeye veya doğrudan belleğe aktarmak mümkündür.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class DosyaOkumaNIO {
public static void main(String[] args) {
Path yol = Paths.get("ornek.txt");
try {
List<String> satirlar = Files.readAllLines(yol);
satirlar.forEach(System.out::println);
} catch (IOException e) {
System.err.println("NIO okuma hatası: " + e.getMessage());
}
}
}
Files.readAllLines() metodu dosyadaki her satırı bir List<String> elemanı olarak döner. Çok büyük dosyalarda bellek tüketimini kontrol altında tutmak için bu metodun yerine Files.lines(Path yol) akış (Stream) metodu kullanılmalıdır.
Java Dosya Yazma Yöntemleri
Bir dosyaya veri yazarken mevcut verinin üzerine yazma (overwrite) ya da sonuna ekleme (append) senaryoları mevcuttur.
BufferedWriter ile Dosyaya Yazma
Metin dosyalarına sıralı ve yüksek performanslı yazma işlemleri için BufferedWriter en güvenilir tercihlerden biridir.
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class DosyaYazmaBuffer {
public static void main(String[] args) {
String dosyaYolu = "cikti.txt";
try (BufferedWriter bw = new BufferedWriter(new FileWriter(dosyaYolu, true))) {
bw.write("Siber güvenlik günlükleri.");
bw.newLine();
bw.write("Java dosya yazma işlemi tamamlandı.");
bw.newLine();
} catch (IOException e) {
System.err.println("Yazma hatası: " + e.getMessage());
}
}
}
FileWriter yapıcısına verilen ikinci parametre olan true, dosyanın sonuna ekleme modunu (append) etkinleştirir. Bu değer belirtilmezse veya false geçilirse, dosya mevcutsa içeriği sıfırlanır ve yeni veri yazılır. newLine() metodu işletim sistemine özgü satır sonu karakterini ekler.
Modern Files Sınıfı ile Dosyaya Yazma
Java 11 ile birlikte gelen Files.writeString() veya standart Files.write() metodları, zahmetsizce dosya yazmayı sağlar.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class DosyaYazmaNIO {
public static void main(String[] args) {
Path yol = Paths.get("hizli_cikti.txt");
String icerik = "Sistem log kaydı: Yetkisiz erişim denemesi tespit edildi.n";
try {
Files.writeString(yol, icerik, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
} catch (IOException e) {
System.err.println("Dosya yazma hatası: " + e.getMessage());
}
}
}
Bu örnekte StandardOpenOption.CREATE dosya yoksa oluşturulmasını, StandardOpenOption.APPEND ise içeriğin dosyanın sonuna iliştirilmesini sağlar.
Dosya İşlemlerinde Dikkat Edilmesi Gerekenler
Girdi ve çıktı süreçleri işletim sistemi kaynaklarıyla doğrudan temas halindedir. Kararlı ve güvenli bir yazılım geliştirmek için aşağıdaki noktalara dikkat edilmelidir:
- Kaynak Yönetimi: Açılan her akış sistemde bir dosya tanıtıcısı (file descriptor) tüketir.
try-with-resourcesyapısı kullanılarak kaynakların işlem sonunda serbest bırakılması garanti altına alınmalıdır. - Karakter Kodlaması: Farklı platformlarda (Windows, Linux, macOS) çalışırken Türkçe karakter sorunları yaşamamak için
StandardCharsets.UTF_8açıkça belirtilmelidir. - Güvenlik ve Path Traversal: Kullanıcıdan alınan dosya isimleri doğrudan işletim sistemi yoluna aktarılmamalıdır.
../gibi dizin atlatma girdileri doğrulanmalı ve normalize edilmelidir. - Bellek Verimliliği: GB ölçeğindeki büyük dosyalar
Files.readAllLinesveyaFiles.readAllBytesile tek seferde belleğe alınmamalı, akış tabanlı yöntemler tercih edilmelidir.
Özet ve Sonraki Adımlar
Java’da dosya işlemleri, geleneksel I/O akışlarından modern NIO.2 API’sine kadar esnek seçenekler sunar. Küçük çaplı işlemler için Files sınıfı hızlı ve pratik çözümler sunarken, yüksek hacimli veya akış tabanlı veri transferlerinde BufferedReader ve BufferedWriter performans avantajı sağlar.
Bir sonraki adımda dosya izinlerinin yönetimi, dizin gezinme (DirectoryStream) ve nesnelerin diske kaydedilmesini sağlayan serileştirme (Serialization) mekanizmalarını inceleyebilirsiniz.


Comments
No comments yet. Be the first to comment.