Konu Anlatımı
Dosya İşlemleri ve Serileştirme
Bir programın verisi yalnız ekranda kalamaz. Kullanıcı notlarını, öğrenci kayıtlarını ya da ölçüm sonuçlarını program kapandığında da korumak gerekir. Bunun yolu verileri bir dosyaya yazmaktır. Metin dosyaları okunabilir olduğu için hem öğrencilerin hem de programcının denetlemesine açıktır. Devam eden konularda nesneleri de dosyaya kaydetme yöntemi öğrenilecektir.
Bu derste ne öğreneceğiz?
Bu konuda önce metin dosyası oluşturma, satır ekleme ve okuma işlemlerini öğreneceğiz. Dosya kaynağının nesne yönelimli yaşam döngüsü içinde nasıl güvenli kapatıldığını göreceğiz. Dosya yolu, varlık denetimi ve hata yönetimi konularını inceleyeceğiz. Karakter kodlamasının dosya boyutuna etkisini sayısal olarak karşılaştıracağız. Son olarak nesne yönelimli verileri JSON biçimine dönüştürerek kalıcı hâle getirmeyi uygulayacağız.
1. Ön bilgi: dosya sistemi ve yol adları
Bir dosya, adı ve uzantısı olan bir bilgi kümesidir. Uzantı, dosyanın türünü belirleyen son bölümüdür; metin dosyalarında .txt, yapılandırılmış metin dosyalarında .json kullanılır. Bir dosyaya erişmek için dosya adının yanında konum da bilinmelidir. Konum, mutlak yol ve göreli yol olmak üzere iki biçimde verilir.
Mutlak yol, kökten başlayarak tüm dizinleri içerir ve programın hangi klasörde çalıştığından bağımsızdır. Göreli yol ise yalnızca dosya adını içerir; dosya, programın çalıştığı klasörde aranır. Öğrenci projelerinde göreli yol kullanmak taşınabilirlik sağlar. Dosya adında ya da klasör adında yazım hatası varsa program dosyayı bulamaz; bu yüzden yol adı sabit bir değişkende tutulmalıdır.
2. Yazma ve okuma temel işlemleri
Metin dosyasına yazmak için StreamWriter, okumak için StreamReader sınıfı kullanılır. Yazma işlemi dosya açılır, metin Write ya da WriteLine ile yazılır ve dosya kapatılır. Okuma işleminde dosya açılır, satırlar ReadLine ile sırayla okunur ve okuma bittiğinde bir boş satır döner.
Write ile yazılan metinde satır sonu eklenmez, WriteLine ile eklenir. Bu nedenle her kaydı ayrı satıra yazmak istiyorsanız WriteLine kullanılmalıdır. Kod içinde yazılan metinde tırnak işareti kaçış karakteriyle belirtilir; aksi hâlde derleme hatası oluşur. Dosya adı uzantısı verilmeden yazılırsa program kendi adıyla bir dosya oluşturur ve dosya metin düzenleyicide garip görünür.
3. Kaynak yönetimi ve using bloğu
Dosya tutamaçları işletim sistemine kaynak bırakan yapılardır. Kapatılmadığında dosya kilitli kalır, silinemez ve yeniden açılamaz. Nesne yönelimli programlamada bu kaynaklar Dispose çağrısıyla serbest bırakılır ve using bloğu bu çağrının blok sonunda otomatik yapılmasını sağlar.
using bloğu kullanmak yalnız estetik değildir; blok içinde bir hata oluşsa bile kaynak yine kapatılır. Elle kapatma yazıldığında ise hata durumunda kapatma satırına hiç ulaşılamaz ve dosya kilitli kalır. Bu nedenle dosya işlemi içeren her metotta using bloğu kullanmak standart alışkanlıktır. Bu kural, yıkıcı metotlar konusundaki IDisposable mantığının dosya üzerindeki karşılığıdır.
4. Hata yönetimi ve dosya varlığının denetimi
Okunacak dosya diskte bulunamayabilir. Bu durumda program kendiliğinden bir hata üretir ve durur. Hata ayıklama sırasında en sık karşılaşılan sorun, dosyanın varlığını denetlemeden okumaya çalışmaktır. Dosyanın varlığı File.Exists ile denetlenir ve sonuç bool türünde bir karşılaştırmada kullanılır.
Dizin oluşturmak için ayrı bir yöntem kullanılır; dizin yoksa dosya yazılamaz. Dosya kopyalamak için de ayrı bir yöntem vardır ve hedef klasörün var olması gerekir. Bu üç denetim birlikte yapıldığında program, veri bulunmadığı durumda anlamlı bir mesajla yolunu bulur. Hata mesajına dosya yolunu yazmak, sorunu bulmayı kolaylaştırır.
5. Karakter kodlaması ve dosya boyutu
Bir bayt kaç karakter saklayabileceği, kullanılan karakter kodlamasına bağlıdır. Tek baytlık kodlamada bir karakter bir bayt, çok baytlık kodlamada ise bir Türkçe karakter iki bayt yer alır. Bu nedenle aynı sayıda karakter, kodlamaya göre farklı dosya boyutu verir.
Örnek olarak 10 karakterlik bir satırda 4 karakter Türkçe karakter ise ve çok baytlık kodlama kullanılıyorsa dosya boyutu 6 × 1 + 4 × 2 = 14 bayt olur. Aynı satırda 40 karakter varsa toplam 40 × 1 = 40 bayt olur; orta büyüklükte bir dosyada bu fark büyük olur. Türkçe karakter içeren kayıtlar yazılacaksa utf-8 ya da unicode kodlaması seçilmelidir; aksi hâlde dosya başka bir bilgisayarda bozuk görünür.
6. Satır sayısı hesabı
Bir dosyanın satır sayısı, dosya boyutu ve ortalama satır uzunluğu biliniyorsa tahmin edilebilir. Bu bilgi gereksinim dosyasının kaç kayıt okunacağını önceden planlamak için kullanılır. Örneğin 1600 baytlık bir dosyada ortalama satır uzunluğu 80 bayt ise 1600 / 80 = 20 satır bulunur.
Tahmin değer gerçek değerle birebir örtüşmez; bazı satırlar daha kısa, bazıları daha uzundur. Bu nedenle tahmin yalnızca kapasite planlaması için kullanılır, kesin kayıt sayısı dosya okunarak öğrenilir. Dosya okunurken satırlar tek tek işlendiği için bellekte tüm dosya birden tutulmaz; bu da büyük dosyalarda bellek tasarrufu sağlar.
7. Serileştirme ve JSON
Bir nesne yalnız bellekte var olan bir yapıdır; program kapanınca kaybolur. Nesneyi kalıcı hâle getirmek için onu metne çevirmek gerekir. Bu işleme serileştirme denir. Ters işlem olan geri yükleme ise metinden yeniden nesne üretir.
Yapılandırılmış metin biçimi olan JSON, alan adlarını ve değerlerini açıkça yazar. Bir kayıt için alan sayısı 5 ise 3 kayıttaki toplam alan sayısı 3 × 5 = 15 olur. Serileştirme sırasında her alanın adı ve tipi korunur; geri yüklemede aynı yapı yeniden kurulur. Öğrenci notu, öğrenci adı gibi sınıf içindeki her veri tipi serileştirilebilir olmalıdır.
Basit örnek
On iki satırlık bir günlük dosyasının her satırı 40 karakterden oluşuyorsa dosyada kaç karakter vardır? Satırlar tek tek eklendiği için toplam 12 × 40 = 480 karakter olur. Eğer bu karakterlerin 40 tanesi Türkçe karakter ise ve çok baytlık kodlama kullanılıyorsa dosya boyutu 480 − 40 + 40 × 2 = 520 bayt olur. Aynı dosya tek baytlık kodlamayla kaydedilseydi 480 bayt olurdu.
İleri örnek
Bir günlük dosyasındaki 1200 kaydı okuyup biçimi hatalı olanları ayıran bir program tasarlayınız. Hatalı kayıt sayısı 1 ise geçerli kayıt sayısı 1200 − 1 = 1199 olur ve kayıtların ne kadarının kullanılabilir durumda olduğu 1199 / 1200 = 0,9992 oranıyla bulunur. Bu oran düşükse dosyanın güvenilirliği yüksek demektir; oranın 0,90'ın altına düşmesi hâlinde kayıt toplama biçimi gözden geçirilmelidir.
Adım adım çözülmüş örnek
Soru: Öğrenci kayıtlarını üç ayrı dosyaya yazan bir program tasarlayınız; her dosyada beş alan bulunsun ve toplam alan sayısını bulunuz.
Adım 1: Her kayıt için beş alanın adı belirlenir: öğrenci no, ad, soyad, not ve devam durumu. Adım 2: Dosya başına kayıt sayısı planlanır ve WriteLine ile her alan ayraçla birleştirilerek yazılır. Adım 3: Üç dosya için toplam alan sayısı 3 × 5 = 15 olarak hesaplanır. Adım 4: Okuma tarafında her satır ayraçla bölünerek alanlar yeniden oluşturulur. Cevap: üç dosyada toplam 15 alan bulunur.
Kavram karşılaştırma tablosu
| İşlem | Yazma | Okuma |
|---|
| Kullanılan sınıf | StreamWriter | StreamReader |
| Temel yöntem | Write veya WriteLine | ReadLine |
| Satır sonu ekleme | WriteLine ekler | Okuma otomatiktir |
| Bitiş belirtilmesi | Flush ya da blok sonu | Boş satır döner |
| Kaynak kapatma | using bloğu | using bloğu |
| Varlık denetimi | Klasör kontrolü gerekir | File.Exists ile denetlenir |
| Karakter kodlaması | utf-8 önerilir | Yazımla aynı olmalıdır |
| Hata riski | Dizin yoksa yazamaz | Dosya yoksa okuyamaz |
Sık Yapılan Hatalar
- Dosyayı kapatmamak: Kapatılmayan dosya işletim sisteminde kilitli kalır, silinemez ve yeniden açılamaz. using bloğu bu hatayı yapısal olarak engeller.
- Dosya varlığını denetlememek: Olmayan bir dosyayı okumaya çalışmak programı durdurur. File.Exists ile denetim yapılmadan okuma yapılmamalıdır.
- Karakter kodlaması uyuşmazlığı: Yazarken tek baytlık, okurken çok baytlık kodlama seçilirse Türkçe karakterler bozuk görünür. İki işlemde de aynı kodlama seçilmelidir.
- Satır sayısını elle yazmak: WriteLine ile yazılan her alan için satır sonu otomatik gelir; ayrıca satır sonu karakteri eklemek kaydı bozar.
- Kayıtları tek satırda birleştirmek: Alan değerlerinin içinde ayraç karakteri varsa kayıt bozulur. Ayraç seçimi ve kaçış işlemi birlikte düşünülmelidir.
- Serileştirilemeyen alan bırakmak: Sınıftaki her alan serileştirilebilir olmalıdır; desteklenmeyen bir tür geri yüklemeyi bozar.
Özet
Metin dosyası oluşturmak için StreamWriter, okumak için StreamReader sınıfı kullanılır. Write satır sonu eklemezken WriteLine her çağrıda satır sonu ekler. Dosya kaynağı using bloğu ile kapatılmalıdır; bu blok, hata oluşsa bile kaynağın serbest bırakılmasını garanti eder. Dosya yolu mutlak ya da göreli olabilir ve dosya varlığı File.Exists ile denetlenmelidir. Karakter kodlaması dosya boyutunu doğrudan etkiler: 10 karakterlik bir satırda 4 Türkçe karakter varsa çok baytlık kodlamada 6 × 1 + 4 × 2 = 14 bayt tutar. Satır sayısı dosya boyutundan ve ortalama satır uzunluğundan tahmin edilebilir; 1600 bayt ve 80 baytlık satırlar için 1600 / 80 = 20 satır bulunur. Nesneleri kalıcı hâle getirmek için serileştirme kullanılır; JSON biçimi alan adlarını açıkça yazar ve geri yüklemede aynı yapıyı yeniden kurar. Yazma ve okuma işlemlerinde aynı karakter kodlaması seçilmelidir.
Kendini dene
Şu soruları cevaplayarak konuyu pekiştir: (1) On iki satırlık, her satırı 45 karakter olan bir dosyada kaç karakter bulunur, bu karakterlerin 30 tanesi Türkçe ise çok baytlık kodlamada dosya boyutu ne olur? (2) Diskte bulunmayan bir dosyayı okumaya çalışan bir program için iki farklı çözüm yolu yazınız. (3) Öğrenci kayıtlarını JSON dosyasına yazan ve geri okuyan bir program tasarlayınız; hangi adımlarda hata oluşabileceğini listeleyiniz. Cevaplarınızı adım adım yazın; puana göre değerlendirmede dosya işlemi seçimi, hesabın doğruluğu, hata yönetimi ve yorum ayrı ayrı değerlendirilir.