SEN1002 · 11. hafta · Files and Streams, Object Serialization

Dosyalar, akışlar ve nesne serileştirme

Files ve Path ile dosya işlemleri, Scanner ile okuma, PrintWriter ve Formatter ile yazma, try-with-resources ve nesneleri ObjectOutputStream ile kaydetmek.

filestreamPathPathsFilesScannerPrintWriterFormatterFileWriterBufferedReadertry-with-resourcesAutoCloseabletext filebinary fileserializationSerializableObjectOutputStreamObjectInputStreamtransient

Program kapandığında değişkenlerdeki her şey kaybolur. Veriyi kalıcı yapmanın yolu dosyaya yazmak. Bu hafta iki tür dosyayla çalışıyorsun: insanın okuyabildiği metin dosyaları (text files) ve Java nesnelerini olduğu gibi saklayan ikili dosyalar (binary files). Sınavda en çok gelenler: checked istisnaların (FileNotFoundException, IOException) derleme hataları, try-with-resources’ın kapanış sırası, Scanner döngüleri ve serileştirmede transient.

Altın kurallar:

  1. Dosya işlemleri checked istisna fırlatır. Ya try/catch ya throws IOException.
  2. Açtığını kapat. En güvenli yol try-with-resources; kapanmayan yazıcı veriyi diske yazmayabilir.
  3. new FileWriter(dosya) eski içeriği siler. Eklemek için new FileWriter(dosya, true).
  4. Serileştirilecek sınıf Serializable olmalı, tuttuğu nesneler de.
  5. transient ve static alanlar dosyaya yazılmaz.

Akış (stream) kavramı

Java’da dosyayla konuşmak bir akış (stream) üzerinden olur: veri bayt bayt ya da karakter karakter akar. İki ana aile var:

Metin (karakter)İkili (bayt)
Ne saklanır?Okunabilir karakterlerHam baytlar, Java nesneleri
OkumaScanner, BufferedReader, Files.readAllLinesObjectInputStream, FileInputStream
YazmaPrintWriter, Formatter, FileWriter, Files.writeObjectOutputStream, FileOutputStream
Not Defteri’nde açılır mı?EvetAnlamsız karakterler

Path ve Files

java.nio.file paketindeki Path bir dosya ya da klasör yolunu temsil eder. Files sınıfı static yardımcı metotlar sunar.

Path scores = Paths.get("scores.txt");               // ya da Path.of("scores.txt")
System.out.println(Files.exists(scores) + " " + Files.exists(Paths.get("nope.txt")));
System.out.println(Files.isDirectory(dir) + " " + Files.isRegularFile(scores));
List<String> lines = Files.readAllLines(scores);
System.out.println(lines.size() + " " + lines.get(1));
System.out.println(scores.getFileName());
Path p2 = Paths.get("data", "2026", "notes.txt");
System.out.println(p2.getFileName() + " " + p2.getNameCount());
Files.write(Paths.get("w.txt"), List.of("one", "two"));
System.out.println(Files.readAllLines(Paths.get("w.txt")));
true false
true true
3 Mert 70
scores.txt
notes.txt 3
[one, two]

(scores.txt bir önceki örnekte yazılan üç satırlık dosya.) Yol göreli (relative) verilirse programın çalıştığı klasöre göre aranır; mutlak yol (absolute) C:\... gibi tam yoldur.

Metin dosyası yazmak

PrintWriter

System.out ile aynı metotlara sahiptir: print, println, printf. Fark, çıktının ekrana değil dosyaya gitmesi.

try (PrintWriter out = new PrintWriter("scores.txt")) {
    out.println("Ece 85");
    out.println("Mert 70");
    out.printf("%s %d%n", "Can", 92);
}   // burada otomatik close: veri diske yazılır

new PrintWriter("scores.txt") dosya yoksa yaratır, varsa içini boşaltır. Yaratamazsa FileNotFoundException (checked) fırlatır.

Kapatmazsan ne olur?

PrintWriter leaky = new PrintWriter(new FileWriter("lost.txt"));
leaky.println("hello");
System.out.println(Files.size(Paths.get("lost.txt")));    // henüz tamponda
leaky.close();
System.out.println(Files.size(Paths.get("lost.txt")) > 0);
0
true

Üzerine yazma ve sona ekleme

try (PrintWriter w = new PrintWriter(new FileWriter("log.txt"))) { w.println("first"); }
try (PrintWriter w = new PrintWriter(new FileWriter("log.txt"))) { w.println("second"); }
System.out.println(Files.readAllLines(Paths.get("log.txt")));
try (PrintWriter w = new PrintWriter(new FileWriter("log.txt", true))) { w.println("third"); }
System.out.println(Files.readAllLines(Paths.get("log.txt")));
[second]
[second, third]

Formatter

Biçimli çıktı için format metodu, printf ile aynı biçim kodlarını kullanır. Tablo gibi hizalı rapor yazmak için idealdir.

try (Formatter f = new Formatter("report.txt")) {
    f.format("%-6s|%5.1f|%03d%n", "Ada", 3.456, 7);
    f.format("%-6s|%5.1f|%03d%n", "Bora", 12.0, 42);
}

Dosyanın satırları (köşeli parantez boşlukları görmek için eklendi):

[Ada   |  3.5|007]
[Bora  | 12.0|042]
BiçimAnlamı
%s, %d, %fString, tam sayı, ondalık
%.2f2 ondalık basamak (yuvarlar)
%8s / %-8s8 karakterlik alan, sağa / sola yaslı
%05d5 hane, başı sıfırlı
%nSatır sonu

Metin dosyası okumak

Scanner ile

Klavyeden okumayı zaten biliyorsun; dosyadan okumak için Scanner’a System.in yerine dosyayı verirsin.

int total = 0, n = 0;
try (Scanner in = new Scanner(new File("scores.txt"))) {
    while (in.hasNext()) {
        String name = in.next();
        int score = in.nextInt();
        total += score; n++;
        System.out.println(name + " -> " + score);
    }
}
System.out.println("avg=" + (double) total / n);
Ece -> 85
Mert -> 70
Can -> 92
avg=82.33333333333333

Dosya yoksa: new Scanner(new File(...)) FileNotFoundException, Files.readAllLines ise NoSuchFileException fırlatır (ikisi de IOException ailesinden).

TUZAK · Scanner ve Türkçe yerel ayar. Scanner sayıları bilgisayarın yerel ayarına (locale) göre okur. Türkçe ayarlı bir makinede 5.5 ondalık sayı sayılmaz, 5,5 sayılır. Denemede aynı dosyanın ortalaması İngilizce ayarda 4.6250, Türkçe ayarda 4,3333 çıktı. Sınavda varsayılan İngilizce düşün; gerçek programda in.useLocale(Locale.US) yaz.

BufferedReader ile satır satır

try (BufferedReader br = Files.newBufferedReader(Paths.get("scores.txt"))) {
    String line;
    int count = 0;
    while ((line = br.readLine()) != null) count += line.length();
    System.out.println(count);
    System.out.println(br.readLine());     // dosya sonu
}
19
null

readLine dosya bitince null döner; döngü koşulu bunu kontrol eder. (“Ece 85” 6, “Mert 70” 7, “Can 92” 6 karakter: 19.)

try-with-resources

try (Kaynak k = ...) { ... } biçimi, blok nasıl biterse bitsin kaynağın close() metodunu otomatik çağırır. Kaynak AutoCloseable (ya da Closeable) arayüzünü uygulamalı; yoksa try-with-resources not applicable to variable type.

class Res implements AutoCloseable {
    private final String name;
    Res(String name) { this.name = name; System.out.println("open " + name); }
    void use() { System.out.println("use " + name); }
    @Override public void close() { System.out.println("close " + name); }
}

try (Res a = new Res("A"); Res b = new Res("B")) {
    a.use();
    b.use();
    throw new IllegalStateException("boom");
} catch (IllegalStateException e) {
    System.out.println("caught " + e.getMessage());
} finally {
    System.out.println("finally");
}
open A
open B
use A
use B
close B
close A
caught boom
finally

Sıra kuralı: açılış yazılış sırasıyla, kapanış ters sırayla, ve kapanış catch’ten önce. close() da istisna fırlatırsa, gövdedeki istisna asıl istisna olarak kalır, close’unki ona “bastırılmış” (suppressed) olarak eklenir: e.getSuppressed()[0].getMessage() denemede close failed verdi.

Nesne serileştirme (object serialization)

Bir nesneyi bütün alanlarıyla birlikte bayt dizisine çevirip dosyaya yazmaya serileştirme, geri okuyup nesneyi yeniden kurmaya deserileştirme denir. Sınıfın java.io.Serializable arayüzünü uygulaması gerekir. Bu arayüzün hiç metodu yoktur; sadece “bu sınıf yazılabilir” diye işaretler (marker interface).

class Student implements Serializable {
    private static final long serialVersionUID = 1L;   // sürüm numarası (önerilir)
    String name; double gpa;
    transient String password;                         // yazılmaz
    static String school = "BAU";                      // yazılmaz (sınıfa ait)
    Student(String n, double g, String p) { name = n; gpa = g; password = p; }
    public String toString() { return name + " " + gpa + " " + password; }
}

try (ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("students.ser")))) {
    oos.writeObject(new Student("Deniz", 3.4, "secret"));
    oos.writeObject(new Student("Ece", 3.9, "pw"));
    oos.writeInt(2026);
}
Student.school = "changed";
try (ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("students.ser")))) {
    Student s1 = (Student) ois.readObject();    // Object döner, cast şart
    Student s2 = (Student) ois.readObject();
    int year = ois.readInt();
    System.out.println(s1);
    System.out.println(s2);
    System.out.println(year + " " + Student.school);
}
Deniz 3.4 null
Ece 3.9 null
2026 changed

Okuma sırası yazma sırasıyla aynı olmalı. readObject iki checked istisna fırlatabilir: IOException ve ClassNotFoundException. Dosyada okunacak nesne kalmadıysa EOFException gelir.

ArrayList gibi koleksiyonlar da Serializable’dır (içindeki elemanlar da Serializable olduğu sürece). Bütün listeyi tek writeObject ile yazabilirsin. Geri okunan liste yeni bir nesnedir: denemede back == names false, back.equals(names) true çıktı.

İçindeki bir alanın sınıfı Serializable değilse yazma anında NotSerializableException fırlar; mesajı sorunlu sınıfın adıdır. Bu bir çalışma zamanı hatası, derleyici yakalamaz.

Sık yapılan hatalar

Yanlış düşünceDoğrusu
new Scanner(new File(...)) serbestçe yazılırFileNotFoundException checked; catch ya da throws
Dosyaya yazdım, kapatmasam da olurTamponda kalabilir, dosya boş görünür
new FileWriter(f) sona eklerSiler; eklemek için ikinci parametre true
try-with-resources kaynakları açılış sırasıyla kapatırTers sırayla, catch’ten önce
Her sınıf try-with-resources’ta kullanılabilirAutoCloseable şart
readObject() doğrudan Student dönerObject döner, cast gerekir
readObject sadece IOException fırlatırClassNotFoundException da
transient alan geri okununca eski değerini alırVarsayılan değer: null, 0, false
static alan nesneyle birlikte kaydedilirKaydedilmez, sınıfa aittir
Serializable sınıfın içindeki her nesne otomatik yazılırO nesnelerin sınıfı da Serializable olmalı
hasNextInt false ise döngü kendiliğinden ilerlerToken’ı next() ile atlamazsan sonsuz döngü

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

Find the compile error.

public static void main(String[] args) {
    Scanner in = new Scanner(new File("data.txt"));
    System.out.println(in.nextLine());
}
Cevabı göster
FileNotFoundException checked, yakalanmamış ya da bildirilmemiş

new Scanner(File) dosya yoksa FileNotFoundException fırlatabilir ve bu checked bir istisna. javac: unreported exception FileNotFoundException; must be caught or declared to be thrown. Çözüm: main(...) throws FileNotFoundException ya da try/catch. Kaynak kapanmadığı için try-with-resources en iyisi.

Soru 2orta

What is printed?

try (PrintWriter out = new PrintWriter("scores.txt")) {
    out.println("Ece 85");
    out.println("Mert 70");
    out.printf("%s %d%n", "Can", 92);
}
int total = 0, n = 0;
try (Scanner in = new Scanner(new File("scores.txt"))) {
    while (in.hasNext()) {
        String name = in.next();
        total += in.nextInt();
        n++;
    }
}
System.out.println("avg=" + (double) total / n);
Cevabı göster
avg=82.33333333333333

Dosyaya üç satır yazılır, try-with-resources bloğu bitince dosya kapanır ve diske yazılır. Scanner her satırda önce ismi (next), sonra sayıyı (nextInt) okur: 85 + 70 + 92 = 247, n = 3. (double) cast bölmeden önce uygulanır: 247 / 3.0 = 82.33333333333333.

Soru 3zor

What is printed?

class Res implements AutoCloseable {
    private final String name;
    Res(String name) { this.name = name; System.out.println("open " + name); }
    public void close() { System.out.println("close " + name); }
}
// in main:
try (Res a = new Res("A"); Res b = new Res("B")) {
    System.out.println("body");
    throw new IllegalStateException("boom");
} catch (IllegalStateException e) {
    System.out.println("caught " + e.getMessage());
} finally {
    System.out.println("finally");
}
Cevabı göster
open A / open B / body / close B / close A / caught boom / finally

Kaynaklar yazıldığı sırayla açılır, TERS sırayla kapanır (B önce). Kapanış catch'ten ÖNCE olur: istisna fırladığında önce kaynaklar kapatılır, sonra catch, en son finally çalışır.

Soru 4orta

What is printed?

class Student implements Serializable {
    String name; double gpa;
    transient String password;
    Student(String n, double g, String p) { name = n; gpa = g; password = p; }
    public String toString() { return name + " " + gpa + " " + password; }
}
// write new Student("Deniz", 3.4, "secret") with ObjectOutputStream,
// then read it back with ObjectInputStream and print it
Cevabı göster
Deniz 3.4 null

transient alanlar serileştirilmez. Nesne geri okunurken o alan varsayılan değerini alır: String için null. Şifre gibi kaydedilmemesi gereken veriler için kullanılır. static alanlar da serileştirilmez; sınıfa aittir, nesneye değil.

Soru 5orta

What happens?

class Address { String city = "Istanbul"; }
class Person implements Serializable {
    String name = "Ali";
    Address address = new Address();
}
// oos.writeObject(new Person());
Cevabı göster
Çalışırken NotSerializableException (Address Serializable değil)

Serileştirme nesneyi, tuttuğu bütün nesnelerle birlikte yazar (object graph). Person Serializable ama içindeki Address değil: writeObject NotSerializableException fırlatır, mesajı sorunlu sınıfın adı. Derleme hatası değil; derleyici bunu kontrol etmez. Çözüm: Address de implements Serializable ya da alanı transient.

Soru 6orta

A PrintWriter writes hello to a file but close() (or flush()) is never called. What is the file size right after println, before closing?

Cevabı göster
0 bayt

PrintWriter (FileWriter üzerinden) veriyi önce bellekteki tampona (buffer) yazar; diske ancak flush ya da close ile gider. Denemede println sonrası Files.size 0 verdi, close sonrası 0'dan büyüktü. Program kapatmadan biterse veri kaybolabilir. try-with-resources bu yüzden önemli.

Soru 7orta

What does the file contain after these three blocks?

try (PrintWriter w = new PrintWriter(new FileWriter("log.txt"))) { w.println("first"); }
try (PrintWriter w = new PrintWriter(new FileWriter("log.txt"))) { w.println("second"); }
try (PrintWriter w = new PrintWriter(new FileWriter("log.txt", true))) { w.println("third"); }
Cevabı göster
second, third (iki satır)

new FileWriter(dosya) dosyayı sıfırdan yazar: ikinci blok "first"i siler. new FileWriter(dosya, true) sona ekleme (append) modudur: "third" eklenir. Sonuç [second, third].

Soru 8orta

What is printed?

// mixed.txt contains:  3 x 4
//                      5.5 6 end
int sum = 0;
try (Scanner in = new Scanner(Paths.get("mixed.txt"))) {
    while (in.hasNext()) {
        if (in.hasNextInt()) sum += in.nextInt();
        else in.next();
    }
}
System.out.println(sum);
Cevabı göster
13

Scanner boşluk ve satır sonlarına göre token'lara böler: 3, x, 4, 5.5, 6, end. hasNextInt sadece tam sayılarda true: 3 + 4 + 6 = 13. Diğer token'ları next() ile atlamak şart; atlamazsan döngü x'te sonsuza kadar döner.

Soru 9zor

What are the two lines of report.txt?

try (Formatter f = new Formatter("report.txt")) {
    f.format("%-6s|%5.1f|%03d%n", "Ada", 3.456, 7);
    f.format("%-6s|%5.1f|%03d%n", "Bora", 12.0, 42);
}
Cevabı göster
Ada | 3.5|007 ve Bora | 12.0|042

%-6s 6 karakterlik alana sola yaslı yazar (Ada + 3 boşluk). %5.1f 5 karakter genişlik, 1 ondalık, sağa yaslı: " 3.5" (3.456 yuvarlandı), " 12.0". %03d 3 hane, başı sıfırla dolu: 007, 042. %n satır sonu.

Soru 10zor

Find the compile error.

static Object load(ObjectInputStream ois) throws IOException {
    return ois.readObject();
}
Cevabı göster
readObject ClassNotFoundException da fırlatır, o bildirilmemiş

readObject iki checked istisna fırlatabilir: IOException ve ClassNotFoundException (okunan sınıf bulunamazsa). javac: unreported exception ClassNotFoundException; must be caught or declared to be thrown. Çözüm: throws IOException, ClassNotFoundException.

Soru 11kolay

Which statement about object serialization is TRUE?

  1. static fields are saved with each object
  2. transient fields are restored with their original values
  3. A class must implement Serializable to be written with writeObject
  4. readObject returns the exact same object (==) that was written
Cevabı göster
A class must implement Serializable to be written with writeObject

Serializable bir işaret arayüzüdür (marker interface), metodu yoktur ama şarttır. static alanlar yazılmaz (denemede yazdıktan sonra değiştirilen static alan, okunduktan sonra da değişik kaldı). transient alanlar varsayılan değerle döner. readObject yeni bir nesne yaratır: back == names false, back.equals(names) true.

Soru 12zor

Write a method double averageOfFile(Path path) throws IOException that reads all tokens of a text file and returns the average of those that are numbers (skip the others). Return 0 if there are no numbers. Use try-with-resources.

Cevabı göster
try (Scanner in = new Scanner(path)) + hasNextDouble / next

Scanner'ı try-with-resources içinde aç; istisna olsa da kapanır. Sayı olmayan token'ları next() ile atla:

public static double averageOfFile(Path path) throws IOException {
    int count = 0;
    double sum = 0;
    try (Scanner in = new Scanner(path)) {
        while (in.hasNext()) {
            if (in.hasNextDouble()) {
                sum += in.nextDouble();
                count++;
            } else {
                in.next();
            }
        }
    }
    return count == 0 ? 0 : sum / count;
}
"3 x 4 / 5.5 6 end" dosyası için (3 + 4 + 5.5 + 6) / 4 = 4.625. Dikkat: Türkçe yerel ayarlı bir bilgisayarda Scanner ondalık ayırıcı olarak virgül bekler, "5.5"i sayı saymaz ve sonuç (3 + 4 + 6) / 3 = 4.3333 olur. Gerekirse in.useLocale(Locale.US);.

Doğrulama: Tüm kod örnekleri JDK 26 ile geçici bir klasörde gerçek dosyalar yaratılarak çalıştırıldı (java verify/SEN1002/W11.java); Türkçe yerel ayar davranışı java -Duser.language=tr -Duser.country=TR -Duser.format=tr W11.java ile denendi. Derleme hataları: verify/SEN1002/err/E11a-c.java.

Deftere not kartı

Metin dosyası: insan okur (Scanner/PrintWriter). İkili (binary): Java okur (ObjectStream). Path p = Paths.get("data", "a.txt"); Files.exists(p) Files.size(p) Files.isDirectory(p) Files.readAllLines(p) → List<String>. Files.write(p, list). Files.writeString(p, s). Okuma: new Scanner(new File("a.txt")) ya da new Scanner(path) → FileNotFoundException/IOException CHECKED. Scanner: hasNext/next, hasNextInt/nextInt, nextLine. Sayı değilse next() ile ATLA. Yazma: new PrintWriter("a.txt") → println, printf. Formatter → format("%-6s%5.1f%n", ...). new FileWriter(f) dosyayı SİLER, new FileWriter(f, true) SONA EKLER. close/flush yoksa veri tamponda kalır, dosya boş olabilir. try (A a = ...; B b = ...) { } → otomatik close, TERS sırada (b sonra a), catch'ten ÖNCE. try-with-resources için AutoCloseable şart. Kapanırken hata → suppressed. Serileştirme: class X implements Serializable; içindeki nesneler de Serializable olmalı. ObjectOutputStream.writeObject(o) / (Tip) ObjectInputStream.readObject() → cast ŞART. readObject: IOException + ClassNotFoundException. Dosya sonu: EOFException. transient ve static alanlar yazılmaz; transient geri gelince varsayılan değer (null, 0).