← Kursa Dön
📄 Text · 12 min

Dosya İşlemleri (NIO.2)

Bir önceki derste Java'nın eski I/O sistemini gördük. Şimdi modern hali: NIO.2 (New I/O 2). Java 7 ile gelen bu API, dosya işlemlerini çok daha kolay ve güçlü hale getiriyor. Eski File sınıfı yerine Path ve Files sınıflarını kullanacağız.


Neden NIO.2?

Eski java.io.File sınıfı birçok soruna sahipti:

  • Hata durumunda exception fırlatmaz, false döner — hatanın sebebini bilemezsin

  • Sembolik link desteği yok

  • Dosya izinleri ile çalışmak zor

  • Büyük dizin listeleme performansı kötü

  • Platform bağımlı davranışlar

NIO.2 bunları çözüyor. Adresler: java.nio.file.Path ve java.nio.file.Files.

Analoji: File eski bir el aleti, Path + Files ise elektrikli alet seti. İkisiyle de iş yapabilirsin ama yenisi daha hızlı, daha güvenli, daha yetenekli.


Path — Dosya Yolunu Temsil Et

Path, dosya sistemindeki bir yolu temsil eder. Dosyanın var olması gerekmez — sadece yolu tutar.

// Path oluşturma
Path p1 = Path.of("dosya.txt");
Path p2 = Path.of("/home", "kullanici", "belgeler", "rapor.txt");
Path p3 = Path.of("C:\\Users\\Ali\\Desktop\\proje");
Path p4 = Paths.get("dosya.txt"); // Eski yol, aynı şey

Path Bilgileri

Path p = Path.of("/home/ali/projeler/Main.java");

System.out.println(p.getFileName());    // Main.java
System.out.println(p.getParent());      // /home/ali/projeler
System.out.println(p.getRoot());        // /
System.out.println(p.getNameCount());   // 4 (home, ali, projeler, Main.java)
System.out.println(p.getName(0));       // home
System.out.println(p.getName(2));       // projeler
System.out.println(p.isAbsolute());     // true

Path Birleştirme ve Çözme

Path base = Path.of("/home/ali");
Path dosya = Path.of("belgeler/rapor.txt");

// resolve — birleştir
Path tam = base.resolve(dosya);
System.out.println(tam); // /home/ali/belgeler/rapor.txt

// resolve ile string
Path tam2 = base.resolve("projeler/Main.java");
System.out.println(tam2); // /home/ali/projeler/Main.java

// relativize — göreceli yol bul
Path p1 = Path.of("/home/ali/belgeler");
Path p2 = Path.of("/home/ali/projeler/java");
System.out.println(p1.relativize(p2)); // ../../projeler/java

// normalize — gereksiz . ve .. temizle
Path karmasik = Path.of("/home/ali/../ali/./belgeler/rapor.txt");
System.out.println(karmasik.normalize()); // /home/ali/belgeler/rapor.txt

// toAbsolutePath — mutlak yola çevir
Path goreceli = Path.of("dosya.txt");
System.out.println(goreceli.toAbsolutePath()); // /mevcut/dizin/dosya.txt

Files — Dosya İşlemleri

Files sınıfı, dosya ve dizin işlemleri için statik metodlar sunar. Path ile birlikte kullanılır.

Dosya/Dizin Kontrolleri

Path p = Path.of("dosya.txt");

System.out.println(Files.exists(p));       // Var mı?
System.out.println(Files.notExists(p));    // Yok mu?
System.out.println(Files.isRegularFile(p)); // Normal dosya mı?
System.out.println(Files.isDirectory(p));   // Dizin mi?
System.out.println(Files.isReadable(p));    // Okunabilir mi?
System.out.println(Files.isWritable(p));    // Yazılabilir mi?
System.out.println(Files.size(p));          // Boyut (byte)

Dosya Okuma

Tüm Satırları Oku

// Küçük dosyalar için — tüm satırları listeye oku
List<String> satirlar = Files.readAllLines(
    Path.of("metin.txt"), 
    StandardCharsets.UTF_8
);

for (String satir : satirlar) {
    System.out.println(satir);
}

Tüm İçeriği String Olarak Oku

// Java 11+ — tek seferde string olarak oku
String icerik = Files.readString(Path.of("metin.txt"), StandardCharsets.UTF_8);
System.out.println(icerik);

Tüm Byte'ları Oku

// İkili dosyalar için
byte[] bytes = Files.readAllBytes(Path.of("resim.png"));
System.out.println("Boyut: " + bytes.length + " byte");

Stream ile Satır Satır Oku (Büyük Dosyalar)

// Büyük dosyalar için — lazy, bellekte tüm dosyayı tutmaz
try (Stream<String> satirlar = Files.lines(Path.of("buyuk.txt"), StandardCharsets.UTF_8)) {
    satirlar
        .filter(s -> !s.isBlank())
        .map(String::trim)
        .forEach(System.out::println);
}

⚠️ `readAllLines` küçük dosyalar için, `lines` büyük dosyalar için! readAllLines tüm dosyayı belleğe yükler. 1GB'lık bir dosyada OutOfMemoryError alırsın. lines ise lazy — satır satır okur.


Dosya Yazma

String Yazma

// Java 11+ — string yaz
Files.writeString(
    Path.of("cikti.txt"),
    "Merhaba Dünya!\nJava NIO.2 ile yazıldı.",
    StandardCharsets.UTF_8
);

Satır Listesi Yazma

List<String> satirlar = List.of(
    "Birinci satır",
    "İkinci satır",
    "Üçüncü satır"
);

Files.write(Path.of("cikti.txt"), satirlar, StandardCharsets.UTF_8);

Dosyaya Ekleme (Append)

// Mevcut dosyanın sonuna ekle
Files.writeString(
    Path.of("log.txt"),
    "Yeni log satırı\n",
    StandardCharsets.UTF_8,
    StandardOpenOption.APPEND,
    StandardOpenOption.CREATE  // Yoksa oluştur
);

Byte Yazma

byte[] veri = {72, 101, 108, 108, 111};
Files.write(Path.of("veri.bin"), veri);

Dosya Oluşturma, Kopyalama, Taşıma, Silme

Dosya Oluşturma

// Boş dosya oluştur
Path yeniDosya = Files.createFile(Path.of("yeni.txt"));

// Geçici dosya oluştur
Path gecici = Files.createTempFile("prefix_", ".tmp");
System.out.println(gecici); // /tmp/prefix_123456.tmp

Dizin Oluşturma

// Tek dizin oluştur (üst dizin var olmalı)
Files.createDirectory(Path.of("yeni_dizin"));

// İç içe dizinler oluştur (mkdir -p gibi)
Files.createDirectories(Path.of("a/b/c/d"));

Dosya Kopyalama

// Basit kopyalama
Files.copy(
    Path.of("kaynak.txt"),
    Path.of("hedef.txt")
);

// Üzerine yazarak kopyala
Files.copy(
    Path.of("kaynak.txt"),
    Path.of("hedef.txt"),
    StandardCopyOption.REPLACE_EXISTING
);

// Dosya özelliklerini de kopyala
Files.copy(
    Path.of("kaynak.txt"),
    Path.of("hedef.txt"),
    StandardCopyOption.REPLACE_EXISTING,
    StandardCopyOption.COPY_ATTRIBUTES
);

Dosya Taşıma / Yeniden Adlandırma

// Taşı
Files.move(
    Path.of("eski_konum/dosya.txt"),
    Path.of("yeni_konum/dosya.txt"),
    StandardCopyOption.REPLACE_EXISTING
);

// Yeniden adlandır (aynı dizinde taşı)
Files.move(
    Path.of("eski_isim.txt"),
    Path.of("yeni_isim.txt")
);

Dosya Silme

// Sil (yoksa NoSuchFileException fırlatır)
Files.delete(Path.of("silinecek.txt"));

// Sil (yoksa exception fırlatmaz)
boolean silindi = Files.deleteIfExists(Path.of("silinecek.txt"));
System.out.println("Silindi: " + silindi);

⚠️ `Files.delete` ile dizin silmek istiyorsan dizin BOŞ olmalı! Dolu dizini silmek için walkFileTree kullanman gerekir (aşağıda göreceğiz).


Dizin İçeriğini Listeleme

list — Bir Seviye

try (Stream<Path> dosyalar = Files.list(Path.of("."))) {
    dosyalar
        .filter(Files::isRegularFile)
        .forEach(System.out::println);
}

walk — Recursive (Alt Dizinler Dahil)

// Tüm alt dizinlerdeki .java dosyalarını bul
try (Stream<Path> dosyalar = Files.walk(Path.of("src"))) {
    List<Path> javaDosyalari = dosyalar
        .filter(p -> p.toString().endsWith(".java"))
        .toList();
    
    javaDosyalari.forEach(System.out::println);
    System.out.println("Toplam: " + javaDosyalari.size() + " dosya");
}

walk ile Derinlik Sınırlama

// Maksimum 2 seviye derinlik
try (Stream<Path> dosyalar = Files.walk(Path.of("proje"), 2)) {
    dosyalar.forEach(System.out::println);
}

find — Koşullu Arama

// 1MB'dan büyük dosyaları bul
try (Stream<Path> sonuclar = Files.find(
    Path.of("."),
    Integer.MAX_VALUE, // Sınırsız derinlik
    (path, attr) -> attr.isRegularFile() && attr.size() > 1_000_000
)) {
    sonuclar.forEach(p -> {
        try {
            System.out.printf("%s (%d KB)%n", p, Files.size(p) / 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
}

walkFileTree — Gelişmiş Dizin Gezme

walk basit durumlar için yeterli. Ama dizin silme, kopyalama gibi karmaşık işlemlerde walkFileTree lazım.

Dolu Dizin Silme

public static void dizinSil(Path dizin) throws IOException {
    Files.walkFileTree(dizin, new SimpleFileVisitor<>() {
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
                throws IOException {
            Files.delete(file);
            return FileVisitResult.CONTINUE;
        }
        
        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) 
                throws IOException {
            Files.delete(dir);
            return FileVisitResult.CONTINUE;
        }
    });
}
// Kullanım
dizinSil(Path.of("silinecek_dizin"));

SimpleFileVisitor dört metod sağlar:

  • preVisitDirectory — dizine girmeden önce

  • visitFile — her dosya için

  • visitFileFailed — dosyaya erişilemezse

  • postVisitDirectory — dizinden çıkarken

Dizin Kopyalama

public static void dizinKopyala(Path kaynak, Path hedef) throws IOException {
    Files.walkFileTree(kaynak, new SimpleFileVisitor<>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) 
                throws IOException {
            Path hedefDir = hedef.resolve(kaynak.relativize(dir));
            Files.createDirectories(hedefDir);
            return FileVisitResult.CONTINUE;
        }
        
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
                throws IOException {
            Path hedefFile = hedef.resolve(kaynak.relativize(file));
            Files.copy(file, hedefFile, StandardCopyOption.REPLACE_EXISTING);
            return FileVisitResult.CONTINUE;
        }
    });
}

Dosya Özellikleri

Path p = Path.of("dosya.txt");

// Temel özellikler
BasicFileAttributes attr = Files.readAttributes(p, BasicFileAttributes.class);

System.out.println("Boyut: " + attr.size());
System.out.println("Oluşturulma: " + attr.creationTime());
System.out.println("Son değişiklik: " + attr.lastModifiedTime());
System.out.println("Son erişim: " + attr.lastAccessTime());
System.out.println("Dizin mi: " + attr.isDirectory());
System.out.println("Sembolik link mi: " + attr.isSymbolicLink());

Son Değişiklik Zamanını Ayarla

Files.setLastModifiedTime(
    Path.of("dosya.txt"),
    FileTime.from(Instant.now())
);

Gerçek Hayat Örnekleri

Log Dosyası Analizi

public static Map<String, Long> logSeviyeSay(Path logDosya) throws IOException {
    try (Stream<String> satirlar = Files.lines(logDosya)) {
        return satirlar
            .filter(s -> !s.isBlank())
            .map(s -> s.split("\\s+")[0]) // İlk kelime = seviye
            .collect(Collectors.groupingBy(
                Function.identity(),
                Collectors.counting()
            ));
    }
}

// Kullanım
Map<String, Long> sonuc = logSeviyeSay(Path.of("app.log"));
System.out.println(sonuc); // {INFO=150, WARN=23, ERROR=5}

CSV Dosyası Okuma

public record Ogrenci(String isim, int yas, double not) {}

public static List<Ogrenci> csvOku(Path dosya) throws IOException {
    try (Stream<String> satirlar = Files.lines(dosya, StandardCharsets.UTF_8)) {
        return satirlar
            .skip(1) // Başlık satırını atla
            .map(s -> s.split(","))
            .map(p -> new Ogrenci(p[0].trim(), Integer.parseInt(p[1].trim()), 
                                   Double.parseDouble(p[2].trim())))
            .toList();
    }
}

Dosya Uzantısına Göre Gruplama

public static Map<String, List<Path>> uzantiyaGoreGrupla(Path dizin) throws IOException {
    try (Stream<Path> dosyalar = Files.walk(dizin)) {
        return dosyalar
            .filter(Files::isRegularFile)
            .collect(Collectors.groupingBy(p -> {
                String isim = p.getFileName().toString();
                int son = isim.lastIndexOf('.');
                return son > 0 ? isim.substring(son) : "uzantısız";
            }));
    }
}

// Kullanım
Map<String, List<Path>> gruplar = uzantiyaGoreGrupla(Path.of("proje"));
gruplar.forEach((uzanti, dosyalar) -> 
    System.out.println(uzanti + ": " + dosyalar.size() + " dosya"));

Eski API vs NIO.2 Karşılaştırma

İşlemEski (File)Yeni (NIO.2)
Yol oluşturnew File("a.txt")Path.of("a.txt")
Var mı?file.exists()Files.exists(path)
OkuBufferedReader + FileReaderFiles.readAllLines(path)
YazBufferedWriter + FileWriterFiles.writeString(path, str)
KopyalaManuel stream kopyalamaFiles.copy(src, dst)
Silfile.delete() (boolean döner)Files.delete(path) (exception fırlatır)
Listelefile.listFiles() (null dönebilir)Files.list(path) (stream döner)

💡 Yeni projlerde her zaman NIO.2 kullan. Eski File API'si sadece legacy kodda karşına çıkar. Dönüşüm: file.toPath() ve path.toFile() ile geçiş yapabilirsin.


Özet

  • Path dosya yolunu temsil eder — resolve, relativize, normalize ile yol işlemleri yapılır

  • Files sınıfı tüm dosya operasyonlarını statik metodlarla sunar

  • `readAllLines` küçük dosyalar, `Files.lines` büyük dosyalar için kullanılır

  • `Files.copy`, `Files.move`, `Files.delete` ile kopyalama/taşıma/silme yapılır

  • `Files.walk` recursive dizin gezme, `walkFileTree` gelişmiş visitor pattern ile gezme sağlar

  • NIO.2, eski File API'sinin yerini almıştır — yeni projelerde daima NIO.2 tercih et