---
title: "Dosya Sistemi API'si"
source: 'https://academia.sh/tr/kurslar/java-standart-kutuphane/dosya-sistemi-apisi'
course: 'Standart Kütüphane ve Akışlar'
language: tr
updated: '2026-08-17T18:09:43+00:00'
license: 'CC BY-SA 4.0'
---

# Dosya Sistemi API'si

Bir yolun ayrıştırılmış bir değer olduğu, normalleştirme ve çözümlemenin dosya sistemine hiç dokunmadan yanıt verdiği iki ayrı sağlayıcıda koşturularak gösterilir. Dizin gezinme sırasının belirtilmemiş olduğu ve varlık denetimiyle açmanın iki ayrı işlem olduğu ölçülür.

Önceki ders bir girdi-çıktı akışının içinden geçen içerikle ilgiliydi. Ama akış açılmadan önce
bir soru daha var: akışın ucundaki şey gerçekten var mı, ve ona giden yol ne anlatıyor? Bu ders dosya
sistemi API'sine bakar — bir yolun kendisinin ayrıştırılmış bir değer olduğunu, bu değer
üzerindeki hangi işlemlerin dosya sistemine hiç dokunmadan yanıt verdiğini ve hangilerinin
gerçekten diske indiğini ölçer.

`Path` bir dizgi değildir. `"a/../b"` yazısı içinde tutulan bir metin gibi görünür, ama
üzerinde çağrılan yöntemler bir metin işleme kütüphanesinin değil, bir **dosya sistemi
sağlayıcısının** kurallarını izler. Bu dersin sorusu kursun sorusuyla aynı biçimde sorulur:
hangi davranış sağlayıcı seçiminden bağımsızdır — yani arayüzün sözüdür; hangisi sağlayıcının
kendi deposuna sorulmadan yanıtlanamaz — yani depo sorgusudur; ve hangi güvence hiçbir
yöntemin vermediği, yalnız çağrıları doğru sırada yapmakla elde edilen bir kuraldır.

Dört bölüm sırayla ilerliyor. İlk ikisi aynı yol nesnesi üzerinde çağrılan iki tür yöntemi
ayırıyor: depoya hiç dokunmayanlar ve depoya gerçekten sorulanlar. Üçüncüsü bir dizinin
listelenme sırasının nereden geldiğini ve neyi garanti etmediğini ölçüyor. Dördüncüsü, tek
bir yöntemin verdiği sözü iki ayrı çağrıya bölmenin bedelini gösteriyor. Dördü de aynı
ilkeye çıkıyor: bir yöntemin adı değil, ne zaman ve hangi kaynağa sorduğu, o yöntemin
verdiği sözü belirliyor.

## Yol Cebiri: Dosya Sistemine Hiç Dokunmadan

- **IO10** — Aynı üç işlem — normalleştirme, çözümleme, dosya adı ayırma — iki ayrı dosya
  sistemi sağlayıcısında çalıştırılır: varsayılan yerel sağlayıcı ve standart kitaplığın kendi zip
  sağlayıcısı (bir zip arşivini bir dosya sistemi gibi açan, standart kitaplığın parçası
  olan sağlayıcı).
- **IO11** — Karşılaştırılan şey iki sonucun **eşitliğidir**; hiçbir yol dizgisinin kendisi
  yorum gerektirmez, çünkü sonuçlar zaten insan tarafından okunabilir kısa dizgilerdir.
  Aynı ölçüm `equals` ve `compareTo` için de tekrarlanır, ve ayrıca yazı olarak aynı iki yol
  farklı sağlayıcılardan geldiğinde `equals`'ın ne dediğine bakılır.

Bu ölçüm için iki ayrı sınıf değil, iki ayrı **sağlayıcı** karşılaştırılıyor: varsayılan
yerel sağlayıcı ve standart kitaplığın kendi zip sağlayıcısı, bir zip arşivini bir dosya sistemi gibi
açan, standart kitaplığın parçası olan `FileSystemProvider` gerçekleştirimi. Kursun önceki
derslerinde "gerçekleştirim" bir `List` ya da `Map` seçimiydi; burada aynı rolü sağlayıcı
seçimi oynuyor — hangi dosya sisteminin arkasında çalıştığın, `Path` arayüzünün üzerinde
çağırdığın yöntemleri değiştirmiyor.

```java
// SaglayiciDeneme.java — yol cebiri iki dosya sistemi saglayicisinda da ayni mi
import java.net.URI;
import java.nio.file.*;
import java.util.Map;

public class SaglayiciDeneme {
    public static void main(String[] args) throws Exception {
        Path arsiv = Path.of("arsiv.zip");
        Files.deleteIfExists(arsiv);
        try (FileSystem zip = FileSystems.newFileSystem(
                URI.create("jar:" + arsiv.toUri()), Map.of("create", "true"))) {

            System.out.printf("%-14s%-20s%-20s%s%n", "islem", "yerel", "zip", "ayni mi");

            String yerelNorm = Path.of("a/./b/../c").normalize().toString();
            String zipNorm = zip.getPath("a/./b/../c").normalize().toString();
            System.out.printf("%-14s%-20s%-20s%s%n", "normalize", yerelNorm, zipNorm,
                    yerelNorm.equals(zipNorm));

            String yerelCoz = Path.of("kok").resolve("alt/dosya.txt").toString();
            String zipCoz = zip.getPath("kok").resolve("alt/dosya.txt").toString();
            System.out.printf("%-14s%-20s%-20s%s%n", "resolve", yerelCoz, zipCoz,
                    yerelCoz.equals(zipCoz));

            String yerelAd = Path.of("dizin/dosya.txt").getFileName().toString();
            String zipAd = zip.getPath("dizin/dosya.txt").getFileName().toString();
            System.out.printf("%-14s%-20s%-20s%s%n", "getFileName", yerelAd, zipAd,
                    yerelAd.equals(zipAd));

            String yerelGoreli = Path.of("a/b/c").relativize(Path.of("a/x")).toString();
            String zipGoreli = zip.getPath("a/b/c").relativize(zip.getPath("a/x")).toString();
            System.out.printf("%-14s%-20s%-20s%s%n", "relativize", yerelGoreli, zipGoreli,
                    yerelGoreli.equals(zipGoreli));

            boolean yerelEsit = Path.of("a/b").equals(Path.of("a/b"));
            boolean zipEsit = zip.getPath("a/b").equals(zip.getPath("a/b"));
            System.out.printf("%-14s%-20s%-20s%s%n", "equals (kendi)", yerelEsit, zipEsit,
                    yerelEsit == zipEsit);

            boolean yerelSira = Path.of("a/b").compareTo(Path.of("a/c")) < 0;
            boolean zipSira = zip.getPath("a/b").compareTo(zip.getPath("a/c")) < 0;
            System.out.printf("%-14s%-20s%-20s%s%n", "compareTo<0", yerelSira, zipSira,
                    yerelSira == zipSira);

            System.out.println("yerel a/b, zip a/b'ye esit mi (ayni yazi, farkli saglayici): "
                    + Path.of("a/b").equals(zip.getPath("a/b")));
        }
        Files.deleteIfExists(arsiv);
    }
}
```

```
islem         yerel               zip                 ayni mi
normalize     a/c                 a/c                 true
resolve       kok/alt/dosya.txt   kok/alt/dosya.txt   true
getFileName   dosya.txt           dosya.txt           true
relativize    ../../x             ../../x             true
equals (kendi)true                true                true
compareTo<0   true                true                true
yerel a/b, zip a/b'ye esit mi (ayni yazi, farkli saglayici): false
```

Altı işlemin altısı da iki sağlayıcıda birebir aynı sonucu veriyor. `normalize` `.` ve `..`
segmentlerini aynı kuralla eritiyor, `resolve` bir yolu bir başkasının üzerine aynı biçimde
ekliyor, `relativize` bir yoldan diğerine giden göreli yolu aynı biçimde hesaplıyor,
`equals` ve `compareTo` da segment listesini karşılaştırıyor. Bu altı işlemin **hiçbiri**
çalışırken sağlayıcının deposuna bakmıyor — zip arşivi henüz boş, yerel dizinde de bu adlarda
hiçbir şey yok, ve yine de altısı da sonuç üretiyor. Sonuç sağlayıcıdan bağımsız olduğu için
bu davranış `Path`'in kendi arayüz sözüdür: yol cebiri bir segment listesi üzerinde çalışan
saf bir hesaptır, deponun içeriğine hiç sormaz.

Son satır bu sözün sınırını gösteriyor. Yazı olarak birebir aynı iki yol — biri yerel
sağlayıcıdan, biri zip sağlayıcısından — `equals` ile karşılaştırıldığında **eşit değil**.
`Path.equals` yalnız segmentlere değil, hangi sağlayıcıya ait olduğuna da bakıyor; bu da
sağlayıcıdan bağımsız bir kuraldır, çünkü iki sağlayıcı da bu kısıtlamayı aynı biçimde
uyguluyor. Yol cebirinin verdiği söz "aynı segmentler aynı sonucu üretir" değil, "aynı
sağlayıcıdaki aynı segmentler aynı sonucu üretir" biçimindedir.

Bu saflığın ölçülebilir bir sonucu var: yol cebiri üzerine yazılan bir kural — bir uzantının
doğrulanması, bir üst dizine çıkışın engellenmesi, bir göreli yolun hesaplanması — hiçbir
dosya oluşturmadan, hiçbir dizin kurmadan sınanabilir. Yukarıdaki ölçümün kendisi de bunu
gösteriyor: zip arşivi boş açıldı, yerel dizinde ilgili adlarda hiçbir şey yoktu, ve altı
karşılaştırmanın altısı da hatasız tamamlandı. Depoya dokunan bir işlem olsaydı, aynı ölçüm
önce bir kurulum gerektirirdi.

## Aynı Sorunun Depoya Dokunan Yanıtı

- **IO12** — Aynı iki sağlayıcıda bu kez `Files.exists` çağrılır. Zip sağlayıcısındaki yola
  önce bir kayıt yazılır, yerel sağlayıcıdaki aynı adlı dosya ise hiç oluşturulmaz.

```java
// DepoyaDokunma.java — depoya dokunan islemler saglayiciya bakar, yol cebiri hicbir zaman bakmaz
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.Map;

public class DepoyaDokunma {
    public static void main(String[] args) throws Exception {
        Path arsiv = Path.of("arsiv.zip");
        Files.deleteIfExists(arsiv);
        try (FileSystem zip = FileSystems.newFileSystem(
                URI.create("jar:" + arsiv.toUri()), Map.of("create", "true"))) {

            Path zipDosya = zip.getPath("kayit.txt");
            Files.writeString(zipDosya, "veri", StandardCharsets.UTF_8);

            Path yerelDosya = Path.of("kayit.txt");
            Files.deleteIfExists(yerelDosya);

            System.out.printf("%-14s%s%n", "saglayici", "Files.exists sonucu");
            System.out.printf("%-14s%s%n", "yerel", Files.exists(yerelDosya));
            System.out.printf("%-14s%s%n", "zip", Files.exists(zipDosya));
        }
        Files.deleteIfExists(arsiv);
    }
}
```

```
saglayici     Files.exists sonucu
yerel         false
zip           true
```

Aynı ad, iki ayrı sağlayıcıda iki ayrı yanıt veriyor — ve bu bir kusur değil, `Files.exists`
gerçekten ne yaptığının kanıtı. Bir önceki bölümdeki altı işlemden farklı olarak `exists`
sağlayıcının deposuna gerçekten sorar; yanıt "hayır" ya da "evet" olabilir ve bu yanıt
depoda o an ne olduğuna bağlıdır. `Files.newInputStream`, `Files.size`, `Files.readAllBytes`,
`Files.isDirectory` aynı ailededir: hepsi çağrıldıkları anda sağlayıcının deposuna gider. Bir
yol nesnesi üzerinde çağrılan yöntemin depoya dokunup dokunmadığını anlamanın kestirme yolu
şudur: sonuç depo boşken de üretilebiliyorsa cebirdir; depo dolu olmayı gerektiriyorsa depo
sorgusudur.

Bu ayrımın pratik bir sonucu var: bir yol nesnesi, gösterdiği dosya hiç yokken bile güvenle
kurulabilir, saklanabilir, başka yollarla birleştirilebilir. Henüz yazılmamış bir çıktı
dizinini `resolve` ile önceden adlandırmak hiçbir işlemi patlatmaz, çünkü cebir hiçbir zaman
depoya sormaz. Sorun ancak `Files.createDirectories` ya da `Files.newOutputStream` gibi
depoya dokunan bir çağrı geldiğinde ortaya çıkabilir — ve o an hatanın nerede arandığı
bellidir, çünkü depoya dokunan yöntemler kısıtlı ve adı bilinen bir kümedir.

Depoya dokunan her yanıtın bir de zaman boyutu var: `exists` çağrıldığı **anda** doğru olan
bir cevap veriyor, o cevabın çağrının bittiği andan sonra da doğru kalacağına dair hiçbir söz
vermiyor. Yol cebiri bu bakımdan daha güçlü bir garanti taşıyor — `normalize` bir kere
hesaplandıktan sonra bir daha hiç geçersiz olmaz, çünkü depoya hiç bakmadı. Depo sorgusunun
sonucu ise bir anlık görüntüdür ve bu dersin son bölümü tam olarak bu anlık görüntünün ne
kadar kısa ömürlü olduğunu ölçüyor.

## Dizin Gezinme Sırası Belirtilmemiştir

- **IO13** — Bir dizine dört dosya belli bir sırayla oluşturulur. `Files.list` ile elde
  edilen gezinme sırası hem oluşturma sırasıyla hem alfabetik sırayla karşılaştırılır.
- **IO14** — Gezinme sırasının kendisi basılmaz; yalnız iki karşılaştırmanın sonucu ve iki
  ayrı gezinme yönteminin birbiriyle tutarlı olup olmadığı basılır. `Files.list`'in
  döndürdüğü şey de bir kaynaktır — arkasında bir dizin tanıtıcısı tutar — ve kaynakla
  deneme (M08/K02 Nesneye Dayalı Java, `java-genellikler/04`) ile açılır.

```java
// GezinmeSirasi.java — dizin gezinme sirasi belirtilmemistir, siralamayi cagiran yapar
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;

public class GezinmeSirasi {
    public static void main(String[] args) throws Exception {
        Path dizin = Path.of("gezinme");
        Files.createDirectories(dizin);
        List<String> olusturmaSirasi = List.of("cok.txt", "az.txt", "orta.txt", "bir.txt");
        for (String ad : olusturmaSirasi) Files.createFile(dizin.resolve(ad));

        List<String> gezinmeListe;
        try (var akis = Files.list(dizin)) {
            gezinmeListe = akis.map(p -> p.getFileName().toString()).collect(Collectors.toList());
        }
        List<String> gezinmeDizinAkisi = new ArrayList<>();
        try (DirectoryStream<Path> dizinAkisi = Files.newDirectoryStream(dizin)) {
            for (Path p : dizinAkisi) gezinmeDizinAkisi.add(p.getFileName().toString());
        }
        List<String> alfabetik = gezinmeListe.stream().sorted().collect(Collectors.toList());

        System.out.println("gezinme sirasi olusturma sirasiyla ayni mi: "
                + gezinmeListe.equals(olusturmaSirasi));
        System.out.println("gezinme sirasi alfabetik siralamayla ayni mi: "
                + gezinmeListe.equals(alfabetik));
        System.out.println("Files.list ile dizin akisi ayni sirayi mi veriyor: "
                + gezinmeListe.equals(gezinmeDizinAkisi));

        for (String ad : olusturmaSirasi) Files.deleteIfExists(dizin.resolve(ad));
        Files.deleteIfExists(dizin);
    }
}
```

```
gezinme sirasi olusturma sirasiyla ayni mi: false
gezinme sirasi alfabetik siralamayla ayni mi: false
Files.list ile dizin akisi ayni sirayi mi veriyor: true
```

Gezinme sırası ne oluşturma sırasıyla ne de alfabetik sırayla örtüşüyor; sağlayıcı kendi
deposunu kendi iç düzenine göre listeliyor ve bu düzen `Files.list`'in belgelenmiş bir sözü
değil. Buna karşılık `Files.list` (bir akış döndüren yöntem) ile `Files.newDirectoryStream`
(bir dizin akışı döndüren, girdi-çıktı akışı duyusunda bir yineleyici arayüzü) aynı gezinmede
**aynı** sırayı veriyor — çünkü ikisi de aynı sağlayıcının aynı iç listeleme çağrısını
sarmalıyor. İki API'nin birbirleriyle tutarlı olması, gezinmenin **belgelenmiş** bir sırası
olduğu anlamına gelmiyor; yalnız aynı sağlayıcı çağrısına dayandıklarını gösteriyor. Sıralı
bir sonuç isteyen çağıran listeyi kendisi sıralamak zorundadır; API bu sözü hiçbir yerde
vermiyor.

Belirsizliğin kaynağı sağlayıcının kendi depolama yapısıdır. Bir dizin girdisi çoğunlukla
adına göre değil, dosya sisteminin kendi iç dizinleme düzenine göre tutulur ve listeleme o
iç düzeni olduğu gibi dolaşır. `Files.list`'in belgesi bu düzeni hiç taahhüt etmez; taahhüt
ettiği tek şey her girdinin listede **bir kez** görüneceğidir. Bu, önceki dersteki tamponlama
ölçümüyle aynı biçimdedir: API'nin vermediği bir sözü kendiliğinden var saymak, kod çalıştığı
sürece fark edilmeyen ama taşındığında ortaya çıkan bir bağımlılık kurar.

## Varlık Denetimi ile Açma İki Ayrı İşlemdir

Bu bölüm önceki bölümün son cümlesini sayısal bir ölçüme çeviriyor. `Files.exists` ile
`Files.newInputStream` iki ayrı depo sorgusudur ve aralarında geçen zaman sıfır değildir;
o boşlukta çalışan kod deponun durumunu değiştirebilir. Ölçüm bunu gerçek bir silme ile
kuruyor, ama kaynağı önemli değil — aynı boşluğu başka bir işlem, başka bir iş parçacığı ya
da kullanıcının kendisi de doldurabilir.

- **IO15** — Bir dosya yazılır, `Files.exists` ile varlığı denetlenir, hemen ardından dosya
  silinir — başka bir kod yolunun araya girdiği kabulü. Denetimden sonra dosya açılmaya
  çalışılır.
- **IO16** — Karşılaştırma için aynı açma çağrısı hiç var olmamış bir dosya üzerinde de
  doğrudan denenir; iki hatanın aynı sınıftan geldiği görülür.

```java
// IkiAdim.java — varlik denetimi ile acma iki ayri islemdir, aralarinda bosluk vardir
import java.nio.file.*;

public class IkiAdim {
    public static void main(String[] args) throws Exception {
        Path dosya = Path.of("gecici-kayit.txt");
        Files.writeString(dosya, "kayit");

        boolean denetimSonucu = Files.exists(dosya);
        Files.delete(dosya);

        String acmaSonucu;
        try {
            Files.newInputStream(dosya).close();
            acmaSonucu = "acildi";
        } catch (NoSuchFileException e) {
            acmaSonucu = "acilamadi: " + e.getClass().getSimpleName();
        }
        System.out.printf("%-18s%s%n", "denetim sonucu", denetimSonucu);
        System.out.printf("%-18s%s%n", "acma sonucu", acmaSonucu);

        String dogrudanSonuc;
        try {
            Files.newInputStream(Path.of("hic-olmayan.txt")).close();
            dogrudanSonuc = "acildi";
        } catch (NoSuchFileException e) {
            dogrudanSonuc = "acilamadi: " + e.getClass().getSimpleName();
        }
        System.out.printf("%-18s%s%n", "dogrudan acma", dogrudanSonuc);
    }
}
```

```
denetim sonucu    true
acma sonucu       acilamadi: NoSuchFileException
dogrudan acma     acilamadi: NoSuchFileException
```

Denetim `true` döndürdükten hemen sonra açma başarısız oluyor, çünkü ikisi arasında dosya
silindi. Bu kusur `Files.exists`'in yalan söylemesinden gelmiyor — denetim çağrıldığı anda
doğru cevabı verdi. Kusur, denetim ile açmanın **iki ayrı çağrı** olmasından, aralarında
geçen zamanda depoyu başka bir şeyin değiştirebilmesinden geliyor. Son satır bunu doğruluyor:
hiç var olmamış bir dosyayı doğrudan açmaya çalıştığımda da aynı istisna, aynı sınıftan
düşüyor. API zaten "aç ya da hata ver" sözünü **tek çağrıda** sunuyor; denetim adımını önce
ekleyen çağırandır, ve o ek adım güvenlik kazandırmıyor, yalnız aradaki boşluğu büyütüyor.
Doğru düzen denetlemeden açmak ve açmanın kendi istisnasını yakalamaktır.

Bu, kütüphanenin bir eksikliği değil — arayüz zaten tek bir çağrıda hem sonucu hem hatayı
taşıyacak biçimde tasarlanmış. Aradaki boşluk yalnız çağıran iki çağrıyı birbirinden ayırıp
arasına başka kod koyduğunda ortaya çıkıyor; ne kadar kısa görünürse görünsün, iki adımın
arasında geçen her satır o boşluğu genişletiyor. Bu, kursun "çağıranın yükümlülüğü" dediği
şeyin en yalın biçimidir: kütüphane yanlış hiçbir şey söylemedi, çağıran doğru bilgiyi yanlış
zamanda kullandı.

## Özet

- `Path` bir dizgi değil ayrıştırılmış bir değerdir; `normalize`, `resolve`, `getFileName`,
  `relativize`, `equals` ve `compareTo` dosya sistemine hiç dokunmadan çalışır ve bu davranış
  iki ayrı sağlayıcıda da aynıdır.
- Yazı olarak aynı iki yol farklı sağlayıcılardan geldiğinde `equals` ile eşit çıkmaz;
  eşitlik segmentlere değil, segmentler artı sağlayıcıya bakar.
- `Files.exists`, `Files.size`, `Files.readAllBytes` gibi işlemler sağlayıcının deposuna
  gerçekten sorar; yanıt depodaki gerçek duruma bağlıdır ve sağlayıcı değişince değişebilir.
- Dizin gezinme sırası ne oluşturma sırasıyla ne alfabetik sırayla örtüşür ve API bu sırayı
  hiçbir yerde belgelemez; sıralı bir sonuç isteyen çağıran listeyi kendisi sıralar.
- İki ayrı gezinme yöntemi aynı sırayı vermesi, sıranın belgelendiği anlamına gelmez — yalnız
  aynı sağlayıcı çağrısına dayandıklarını gösterir.
- Sınırlayıcı ölçüm: varlık denetimi ile açma iki ayrı işlemdir. Aralarında dosya silinirse
  denetim geçer, açma düşer; API zaten tek çağrıda "aç ya da hata ver" sunar, ek denetim
  adımı çağıranın kendi eklediği bir boşluktur.

## Sonraki Adım

Bu derste bir dosyanın var olup olmadığı, hangi baytları taşıdığı ölçüldü — ama bir nesnenin
kendisi bayta çevrildiğinde ne olduğu hiç sorulmadı. Sıradaki ders dizileştirmeye bakar: bir
nesne bayt dizisine döküldüğünde ve geri okunduğunda, o nesnenin yapıcısının hiç çalışmadığını
ve sınıfın kurduğu değişmezlerin bu yüzden dışarıdan verilen baytlarla kırılabildiğini ölçer.
