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.
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:
- Dosya işlemleri checked istisna fırlatır. Ya try/catch ya
throws IOException. - Açtığını kapat. En güvenli yol try-with-resources; kapanmayan yazıcı veriyi diske yazmayabilir.
new FileWriter(dosya)eski içeriği siler. Eklemek içinnew FileWriter(dosya, true).- Serileştirilecek sınıf
Serializableolmalı, tuttuğu nesneler de. transientvestaticalanlar 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 karakterler | Ham baytlar, Java nesneleri |
| Okuma | Scanner, BufferedReader, Files.readAllLines | ObjectInputStream, FileInputStream |
| Yazma | PrintWriter, Formatter, FileWriter, Files.write | ObjectOutputStream, FileOutputStream |
| Not Defteri’nde açılır mı? | Evet | Anlamsı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çim | Anlamı |
|---|---|
%s, %d, %f | String, tam sayı, ondalık |
%.2f | 2 ondalık basamak (yuvarlar) |
%8s / %-8s | 8 karakterlik alan, sağa / sola yaslı |
%05d | 5 hane, başı sıfırlı |
%n | Satı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.5ondalık sayı sayılmaz,5,5sayılır. Denemede aynı dosyanın ortalaması İngilizce ayarda4.6250, Türkçe ayarda4,3333çıktı. Sınavda varsayılan İngilizce düşün; gerçek programdain.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üşünce | Doğrusu |
|---|---|
new Scanner(new File(...)) serbestçe yazılır | FileNotFoundException checked; catch ya da throws |
| Dosyaya yazdım, kapatmasam da olur | Tamponda kalabilir, dosya boş görünür |
new FileWriter(f) sona ekler | Siler; eklemek için ikinci parametre true |
| try-with-resources kaynakları açılış sırasıyla kapatır | Ters sırayla, catch’ten önce |
| Her sınıf try-with-resources’ta kullanılabilir | AutoCloseable şart |
readObject() doğrudan Student döner | Object döner, cast gerekir |
| readObject sadece IOException fırlatır | ClassNotFoundException da |
| transient alan geri okununca eski değerini alır | Varsayılan değer: null, 0, false |
| static alan nesneyle birlikte kaydedilir | Kaydedilmez, sınıfa aittir |
| Serializable sınıfın içindeki her nesne otomatik yazılır | O nesnelerin sınıfı da Serializable olmalı |
| hasNextInt false ise döngü kendiliğinden ilerler | Token’ı next() ile atlamazsan sonsuz döngü |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
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
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.
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
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.
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
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.
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 itCevabı göster
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.
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
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.
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
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.
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
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].
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
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.
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
%-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.
Find the compile error.
static Object load(ObjectInputStream ois) throws IOException {
return ois.readObject();
}Cevabı göster
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.
Which statement about object serialization is TRUE?
- static fields are saved with each object
- transient fields are restored with their original values
- A class must implement Serializable to be written with writeObject
- readObject returns the exact same object (==) that was written
Cevabı göster
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.
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
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.