---
title: 'Akış Tabanlı Girdi/Çıktı'
source: 'https://academia.sh/tr/kurslar/java-standart-kutuphane/akis-tabanli-girdi-cikti'
course: 'Standart Kütüphane ve Akışlar'
language: tr
updated: '2026-08-17T18:09:43+00:00'
license: 'CC BY-SA 4.0'
---

# Akış Tabanlı Girdi/Çıktı

Bayt akışının write(int) için düşük sekiz bitten başka söz vermediği, karakter akışının bir kodlamaya yaslandığı ve tamponlamanın sözü değil yalnız alt akışa varan çağrı sayısını değiştirdiği ölçülür. Sınırlayıcı ölçüm, boşaltılmamış bir tamponun veriyi eksik bıraktığını gösterir.

B payının son dersi yokluğu bir tipe taşıdı: `Optional` boş olduğunda okuma yine çalışma
zamanında düşüyordu, denetim ortadan kalkmadı, yalnız yeri değişti. Bu konu aynı soruyu **dış
dünyaya** sorar. Bir bayt dizisini ya da bir metni okurken kütüphanenin sana verdiği söz nedir,
ve o sözü kimin taşıdığını — arayüzün mü, seçtiğin sınıfın mı, yoksa senin mi — çağrı yazımına
bakarak ayırt edebilir misin?

Bu derste iki ayrı aile karşılaştırılır: bayt akışı ve karakter akışı. İkisi de `java.io`
girdi-çıktı akışıdır; Java'nın `Stream` veri akışı ayrı bir API'dir ve bu konu boyunca hep
niteleyiciyle anılacaktır. Soru hep aynı biçimde sorulur: bir davranış iki ayrı
gerçekleştirimde de görülüyorsa arayüzün sözüdür; yalnız birinde görülüyorsa seçilen sınıfa
aittir; hiç görülmüyor ama kod hâlâ çalışıyorsa çağıranın uyduğu bir kuraldır.

Beş bölüm sırayla ilerliyor. İlk ikisi bayt seviyesinde neyin garanti edildiğini ölçüyor —
yazmada ve okumada. Üçüncüsü tamponlamanın bu iki sözü hiç bozmadan yalnız çağrı sayısını
değiştirdiğini gösteriyor. Dördüncüsü karakter akışının bayt akışına eklediği tek şeyin, yani
kodlamanın, çağırana yüklediği kararı ölçüyor. Beşincisi ise hiçbir sınıfın vermediği, yalnız
çağıranın uyması gereken bir kuralı: bir tamponun boşaltılması.

## Bayt Akışının Verdiği Tek Söz

- **IO1** — `OutputStream.write(int)` bir tamsayı alır ama yalnız düşük sekiz bitini yazar;
  kalan yirmi dört bit yok sayılır. Bu ölçüm, iki ayrı gerçekleştirimin aynı değeri yazıp aynı
  düşük sekiz biti üretip üretmediğine bakar.
- **IO2** — Yazılan bayt geri okunurken `& 0xFF` ile işaretsiz tamsayıya çevrilir; imzalı
  `byte` değeri doğrudan karşılaştırılmaz.

```java
// TekSoz.java — bayt akisinin write(int) icin verdigi tek soz: dusuk sekiz bit
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;

public class TekSoz {
    public static void main(String[] args) throws Exception {
        int deger = 321;
        int beklenen = deger & 0xFF;
        System.out.printf("%-24s%-10s%s%n", "gerceklestirim", "yazilan", "beklenenle ayni mi");

        ByteArrayOutputStream dizi = new ByteArrayOutputStream();
        dizi.write(deger);
        int diziSonuc = dizi.toByteArray()[0] & 0xFF;
        System.out.printf("%-24s%-10d%s%n", "ByteArrayOutputStream", diziSonuc, diziSonuc == beklenen);

        Path dosya = Path.of("tek-soz.bin");
        try (OutputStream dosyaAkisi = new FileOutputStream(dosya.toFile())) {
            dosyaAkisi.write(deger);
        }
        int dosyaSonuc = Files.readAllBytes(dosya)[0] & 0xFF;
        System.out.printf("%-24s%-10d%s%n", "FileOutputStream", dosyaSonuc, dosyaSonuc == beklenen);
        Files.delete(dosya);
    }
}
```

```
gerceklestirim          yazilan   beklenenle ayni mi
ByteArrayOutputStream   65        true
FileOutputStream        65        true
```

Yazılan değer 321; ikilik tabanda alt sekiz biti `01000001`, yani 65. Bellekte tutan bir
gerçekleştirim ile bir dosyaya yazan gerçekleştirim, aralarında hiçbir ortak kod satırı
olmamasına rağmen aynı düşük sekiz biti üretiyor. Bu, `OutputStream` soyut sınıfının
belgelediği bir sözdür — gerçekleştirim seçimi burada hiçbir şeyi değiştirmiyor, çünkü
davranış zaten üst sınıfın kendisinde tanımlı.

Bayt akışının verdiği söz tam olarak bu kadardır: sekiz bitlik bir birimi olduğu gibi taşımak.
Değerin ne anlama geldiği — bir karakter mi, bir sayı mı, bir görüntünün parçası mı —
akışın hiç bilmediği bir şeydir. Bayt akışı hiçbir **yorum** sözü vermez; yalnız birim taşır.
`OutputStream` bir Java `interface`'i değil soyut sınıftır, ama ölçünün sorduğu soruyu
değiştirmiyor: davranış üst tipte tanımlıysa, o tip bir arayüz gibi konuşuyor demektir —
onu konuşan her alt sınıf sözü aynen taşımak zorunda.

## Okuma Ucunda Aynı Söz

- **IO3** — `InputStream.read()` de bir sözdür: okunan bayt işaretsiz bir tamsayı olarak,
  0 ile 255 arasında döner; girdi-çıktı akışı bittiğinde dönen değer bir bayt değil, akış
  dışı bir işaretçidir: **-1**.
- **IO4** — Ölçülen dizi üç bayt taşıyor ve üçüncüsü, imzalı `byte` olarak okunsaydı negatif
  çıkacak bir değer (255). `read()`'in işaretsize çevirmesi bu değeri de doğru taşıyıp
  taşımadığı burada görülüyor.

```java
// Okuma.java — read() icin verdigi tek soz: 0-255 arasi deger, sonda -1
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

public class Okuma {
    static List<Integer> oku(InputStream a) throws Exception {
        List<Integer> degerler = new ArrayList<>();
        int d;
        while ((d = a.read()) != -1) degerler.add(d);
        degerler.add(a.read());
        return degerler;
    }

    public static void main(String[] args) throws Exception {
        byte[] veri = { 10, (byte) 200, (byte) 255 };

        List<Integer> diziSonuc = oku(new ByteArrayInputStream(veri));

        Path dosya = Path.of("okuma.bin");
        Files.write(dosya, veri);
        List<Integer> dosyaSonuc;
        try (InputStream dosyaAkisi = new FileInputStream(dosya.toFile())) {
            dosyaSonuc = oku(dosyaAkisi);
        }
        Files.delete(dosya);

        System.out.printf("%-24s%s%n", "gerceklestirim", "okunan degerler (son ikisi: son bayt, dosya sonu)");
        System.out.printf("%-24s%s%n", "ByteArrayInputStream", diziSonuc);
        System.out.printf("%-24s%s%n", "FileInputStream", dosyaSonuc);
        System.out.println("iki dizi ayni mi: " + diziSonuc.equals(dosyaSonuc));
    }
}
```

```
gerceklestirim          okunan degerler (son ikisi: son bayt, dosya sonu)
ByteArrayInputStream    [10, 200, 255, -1]
FileInputStream         [10, 200, 255, -1]
iki dizi ayni mi: true
```

Üç bayttan üçüncüsü, imzalı `byte` olarak yorumlansaydı **-1** çıkardı; `read()` onu
işaretsiz bir değere çevirip **255** olarak döndürüyor. Bellekteki dizi de dosya da aynı üç
değeri ve ardından aynı işaretçiyi veriyor. Bu `-1` okunmuş bir bayt değildir — hiçbir gerçek
bayt bu değeri taşıyamaz, çünkü işaretsize çevrilmiş bir bayt her zaman 0 ile 255 arasındadır.
`-1`, akışın **bittiğini** söyleyen, veri uzayının dışında duran bir işarettir.

Yazmada düşük sekiz bitin korunması ile okumada işaretsize çevirme ve dosya sonu işareti,
birlikte bayt akışının bütün sözleşmesini kuruyor. İkisi de gerçekleştirimden bağımsız, çünkü
ikisi de `InputStream` ve `OutputStream`'in kendisinde tanımlı; bir alt sınıf bu davranışı
değiştiremez, yalnız *nasıl* sağladığını — bellekte mi, diskte mi — seçebilir.

## Tamponlamanın Sözü Değil Çağrı Sayısını Değiştirmesi

- **IO5** — Alt akışa varan her yazma çağrısını sayan bir sarmalayıcı yazılır; bu sarmalayıcı
  yalnız sayaç tutar, veriyi değiştirmez.
- **IO6** — Aynı beş parça, önce doğrudan bu sarmalayıcıya, sonra `BufferedOutputStream`
  üzerinden aynı sarmalayıcıya yazılır. Karşılaştırılan iki şey ayrıdır: alt akışa varan çağrı
  sayısı ve alt akışın topladığı içerik.

```java
// SayacAkis.java — alttaki akisa varan yazma cagrisini sayan sarmalayici (yardimci sinif, main yok)
import java.io.IOException;
import java.io.OutputStream;

class SayacAkis extends OutputStream {
    private final OutputStream alt;
    int cagriSayisi = 0;

    SayacAkis(OutputStream alt) { this.alt = alt; }

    @Override public void write(int b) throws IOException {
        cagriSayisi++;
        alt.write(b);
    }

    @Override public void write(byte[] veri, int bas, int uzunluk) throws IOException {
        cagriSayisi++;
        alt.write(veri, bas, uzunluk);
    }

    @Override public void flush() throws IOException { alt.flush(); }
    @Override public void close() throws IOException { alt.close(); }
}
```

```java
// Tamponlama.java — tamponlamanin sozu degil, alt akisa varan cagri sayisini degistirdigi
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.util.List;

public class Tamponlama {
    static final List<byte[]> PARCALAR = List.of(
            "veri".getBytes(), "-".getBytes(), "kaydi".getBytes(),
            "-".getBytes(), "01".getBytes());

    public static void main(String[] args) throws Exception {
        ByteArrayOutputStream altDogrudan = new ByteArrayOutputStream();
        SayacAkis sayacDogrudan = new SayacAkis(altDogrudan);
        for (byte[] parca : PARCALAR) sayacDogrudan.write(parca, 0, parca.length);
        sayacDogrudan.close();

        ByteArrayOutputStream altTamponlu = new ByteArrayOutputStream();
        SayacAkis sayacTamponlu = new SayacAkis(altTamponlu);
        try (BufferedOutputStream tamponlu = new BufferedOutputStream(sayacTamponlu)) {
            for (byte[] parca : PARCALAR) tamponlu.write(parca, 0, parca.length);
        }

        System.out.printf("%-14s%-18s%s%n", "yazim", "alt cagri sayisi", "teslim edilen icerik");
        System.out.printf("%-14s%-18d%s%n", "dogrudan", sayacDogrudan.cagriSayisi,
                altDogrudan.toString());
        System.out.printf("%-14s%-18d%s%n", "tamponlu", sayacTamponlu.cagriSayisi,
                altTamponlu.toString());
        System.out.println("icerik ayni mi: " + altDogrudan.toString().equals(altTamponlu.toString()));
    }
}
```

```
yazim         alt cagri sayisi  teslim edilen icerik
dogrudan      5                 veri-kaydi-01
tamponlu      1                 veri-kaydi-01
icerik ayni mi: true
```

Doğrudan yazımda beş parça, sayaç akışına beş ayrı çağrı olarak ulaşıyor: her `write` alt
akışa hemen düşüyor. Tamponlu yazımda aynı beş parça sayaç akışına **bir** çağrı olarak
ulaşıyor, çünkü `BufferedOutputStream` parçaları kendi belleğinde biriktirip yalnız
boşalttığında alt akışı çağırıyor. İki yazımın da teslim ettiği içerik birebir aynı.

Bu ölçüm tamponlamanın ne değiştirip ne değiştirmediğini ayırıyor. `OutputStream`'in verdiği
söz "yazdığın baytlar sonunda alt akışa ulaşacak" biçimindedir; "her `write` çağrısı alt akışa
anında dokunacak" biçiminde değildir. Tamponlama bu ikinci cümleyi hiç ihlal etmiyor, çünkü o
cümle zaten sözün bir parçası değildi. Değişen şey yalnız çağıranın göremeyeceği bir sayı:
alt akışa varan çağrı sayısı.

Bu yüzden tamponlama bir garanti değil bir seçimdir. Alt akış gerçekten pahalıysa — bir dosya,
bir ağ bağlantısı — beş ayrı çağrı beş ayrı sistem çağrısına dönüşebilir; bir tampon araya
girdiğinde aynı iş tek çağrıya iner. Seçim çağırana ait olduğu için `BufferedOutputStream`
hiçbir zaman kendiliğinden boşalmaz: tamponu ne zaman dolduracağına, ne zaman erken
boşaltacağına yalnız çağıran karar verir. Bu dersin son bölümündeki sınırlayıcı ölçüm tam
olarak bu kararın atlandığı durumu gösteriyor.

## Karakter Akışının Kodlamaya Yaslanması

- **IO7** — Bir metin tek bir kodlamayla bayta çevrilip yazılır. Aynı bayt dizisi iki ayrı
  kodlamayla geri okunur: yazıldığı kodlamayla ve başka bir kodlamayla.
- **IO8** — Karşılaştırılan üç şey: bayt sayısı (sabit kalır, çünkü kaynak aynıdır), karakter
  sayısı ve okunan metnin yazılanla eşitliği. Metnin kendisi değil, uzunluk ve eşitlik basılır.

```java
// Kodlama.java — karakter akisinin verdigi soz kodlamaya baglidir, bayt akisininki degildir
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class Kodlama {
    public static void main(String[] args) throws Exception {
        String turkce = "ağırlık";
        Path dosya = Path.of("kodlama.bin");

        byte[] bayt = turkce.getBytes(StandardCharsets.UTF_8);
        Files.write(dosya, bayt);

        String ayniKodlama = new String(Files.readAllBytes(dosya), StandardCharsets.UTF_8);
        String farkliKodlama = new String(Files.readAllBytes(dosya), StandardCharsets.ISO_8859_1);

        System.out.printf("%-26s%-12s%-16s%s%n", "okuma kodlamasi", "bayt sayisi", "karakter sayisi", "yazilanla ayni mi");
        System.out.printf("%-26s%-12d%-16d%s%n", "UTF_8 (yazildigi gibi)", bayt.length,
                ayniKodlama.length(), ayniKodlama.equals(turkce));
        System.out.printf("%-26s%-12d%-16d%s%n", "ISO_8859_1 (baska kodlama)", bayt.length,
                farkliKodlama.length(), farkliKodlama.equals(turkce));

        Files.delete(dosya);
    }
}
```

```
okuma kodlamasi           bayt sayisi karakter sayisi yazilanla ayni mi
UTF_8 (yazildigi gibi)    10          7               true
ISO_8859_1 (baska kodlama)10          10              false
```

Yedi harflik metin UTF-8 ile on bayt üretiyor, çünkü `ğ` ve `ı` bu kodlamada iki baytla
gösteriliyor. Aynı on bayt UTF-8 ile geri okunduğunda yedi karaktere dönüyor ve yazılanla
birebir eşleşiyor. Aynı on bayt ISO-8859-1 ile okunduğunda her bayt kendi başına bir karakter
sayılıyor ve sonuç on karakter — yazılan metinle eşleşmiyor.

Bu ölçüm çağıranın bir kuralını gerçekten kırıyor: yazma ve okuma kodlamasının aynı olması.
Kırılmanın sonucu ne bir istisnadır ne de bir uyarı; **sessiz** bir sonuçtur. Program hatasız
çalışır, bir dizgi üretir, ve o dizgi yanlıştır. Bayt akışının hiçbir yorum sözü vermediği
önceki bölümde kuruldu; karakter akışı tam olarak bu boşluğu dolduruyor, ama doldurduğu
kodlamayı **sen** vermek zorundasın. Kodlama verilmediğinde derleyici bir varsayılana
düşer ve karar ortama kalır — bu derste her okuma ve yazmada kodlamanın açıkça verilmesinin
nedeni tam olarak yukarıdaki tablodur: iki taraf farklı kodlama kullandığında hata hiçbir
yerde durmaz, yalnız sayı değişir.

Bu yükün nerede taşındığı da ölçülebilir bir seçimdir. `OutputStreamWriter` ve
`InputStreamReader` — bayt akışını karakter akışına çeviren iki sınıf — kurucularında bir
`Charset` parametresi ister; `Files.write` ve `Files.readAllBytes` ise kodlamadan tamamen
habersizdir, çünkü ikisi de yalnız bayt taşır ve kodlama kararını çağırana, yukarıdaki gibi
`getBytes` ve `String` kurucusuna bırakır. Hangi katmanda verilirse verilsin, karar sözün bir
parçası değildir; her okuma ve her yazma kendi kodlamasını açıkça taşımak zorundadır.

## Boşaltılmamış Tampon

- **IO9** — Bir `BufferedOutputStream` yazıldıktan hemen sonra, `flush` çağrılmadan, alttaki
  akışın içeriğine doğrudan bakılır. İkinci ölçümde aynı adımlar `flush` ile tamamlanır.

```java
// Bosaltma.java — bosaltilmamis tampon veriyi tasimaz, bosaltma karari cagirana aittir
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;

public class Bosaltma {
    public static void main(String[] args) throws Exception {
        ByteArrayOutputStream altKapatilmadan = new ByteArrayOutputStream();
        BufferedOutputStream tamponluKapatilmadan = new BufferedOutputStream(altKapatilmadan);
        tamponluKapatilmadan.write("teslim edilmeyecek".getBytes());

        ByteArrayOutputStream altBosaltilan = new ByteArrayOutputStream();
        BufferedOutputStream tamponluBosaltilan = new BufferedOutputStream(altBosaltilan);
        tamponluBosaltilan.write("teslim edilecek".getBytes());
        tamponluBosaltilan.flush();

        System.out.printf("%-26s%-12s%s%n", "olcum", "alt uzunluk", "alt akista veri var mi");
        System.out.printf("%-26s%-12d%s%n", "flush cagrilmadan",
                altKapatilmadan.size(), altKapatilmadan.size() > 0);
        System.out.printf("%-26s%-12d%s%n", "flush cagrildiktan sonra",
                altBosaltilan.size(), altBosaltilan.size() > 0);
    }
}
```

```
olcum                     alt uzunluk alt akista veri var mi
flush cagrilmadan         0           false
flush cagrildiktan sonra  15          true
```

`flush` çağrılmadan alttaki akışın uzunluğu sıfır: on dokuz baytlık metin hâlâ
`BufferedOutputStream`'in kendi belleğinde bekliyor, alt akışa hiç dokunulmamış. `flush`
çağrıldıktan sonra aynı düzenek on beş baytı eksiksiz teslim ediyor. Aradaki fark kodun
doğruluğunda değil, **bir çağrının yapılıp yapılmadığındadır** — ve o çağrıyı yapmak
tamamen çağıranın kararıdır.

Kaynakla deneme (M08/K02 Nesneye Dayalı Java, `java-genellikler/04`) bu çağrıyı otomatikleştirir:
bir `BufferedOutputStream` `try (...)` içinde açılırsa, bloktan çıkarken `close` çalışır ve
`close` içeriden `flush`'ı çağırır. Ama bu otomasyonun devreye girmesi için tamponlu akışın
**kaynak olarak bildirilmiş olması** gerekir; yukarıdaki ilk ölçümde `tamponluKapatilmadan`
hiçbir `try (...)` bloğuna girmediği için kapatma hiç çalışmadı ve veri kayboldu. Kaynakla
deneme kaybı önler, ama önleyecek satırı yazmak yine çağırana kalır.

Bazı sınıflar bu kararı kendi üzerlerine alır: `PrintWriter`'ın bir kurucusu her satır
sonunda otomatik boşaltma isteyip istemediğini bir `boolean` parametreyle sorar.
`BufferedOutputStream`'in böyle bir seçeneği yok — o hep manuel kalır. Bu fark bile aynı
kuralı doğruluyor: otomatik boşaltma bir sınıfın *tercih ettiği* bir davranıştır, girdi-çıktı
akışı sınıflarının **hepsinin** uyduğu bir arayüz sözü değildir. Hangi sınıfı seçtiğin, boşaltma
kararını kendine mi bırakacağını yoksa sınıfa mı devredeceğini de belirliyor.

## Özet

- Bayt akışının `write(int)` için verdiği tek söz düşük sekiz biti taşımaktır; bu söz farklı
  gerçekleştirimlerde aynı çıkıyor, çünkü üst sınıfın kendisinde tanımlıdır.
- Tamponlama alt akışa varan çağrı sayısını değiştirir, teslim edilen içeriği değiştirmez;
  beş çağrı bire iniyor ama sonuç birebir aynı kalıyor.
- Karakter akışı bir kodlamaya yaslanır; bayt akışı hiçbir yorum sözü vermez. Kodlama
  verilmediğinde karar ortama düşer.
- Yazma ve okuma kodlaması ayrıldığında hata hiçbir yerde durmaz: yedi karakterlik bir metin
  sessizce on karaktere dönüşür.
- Sınırlayıcı ölçüm: boşaltılmamış bir tampon veriyi eksik bırakır. Kaynakla deneme bu kaybı
  önler, ama tamponlu akışı kaynak olarak bildirme kararı çağırana aittir.

## Sonraki Adım

Bu derste ölçülen her şey bir akışın içinden geçen içerikle ilgiliydi: hangi bayt yazıldı,
hangi karakter okundu. Ama bir akış açılmadan önce bir soru daha var: akışın ucundaki şey
gerçekten var mı, ve ona giden yol ne anlatıyor? Sıradaki 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.
