Dosyalar
Ne kadar güçlü olsalar da; önceki bölümde gördüğümüz >, <, ve | her duruma uygun değildir. Çünkü her program işini yalnızca standart giriş ve çıkışla etkileşerek yapamaz.
Örneğin öğrenci kayıtları ile ilgilenen bir program, standart çıkışını kullanıcıya bir komut menüsü göstermek için kullanıyor olabilir. Standart girişini de kullanıcıdan komut almak için kullandığını düşünürsek, böyle bir programın kayıtlarını tuttuğu öğrenci bilgilerini yazmak için bir dosyaya ihtiyacı olacaktır.
Bu bölümde işletim sisteminin klasörlerde barındırdığı dosyalara yazmayı ve dosyalardan okumayı öğreneceğiz.
Temel kavramlar
Bu dersin yazıldığı tarihlerde (Eylül 2009) Phobos kütüphanesinde dosyalarla ilgili iki farklı olanak bulunuyor: std.stdio modülündeki File yapısı ve std.stream modülündeki File sınıfı. Henüz ne yapıları gösterdim, ne de sınıfları. O yüzden, bu iki File'ın kurulmaları sırasındaki söz dizimleri konusunda bazı kabuller yapmanızı bekleyeceğim.
İki olanaktan birisinin ötekisine göre kesin bir üstünlüğü bulunmuyor. Ben sonunda seçimi size bırakacağım. Örneklere geçmeden önce, her ikisi için de geçerli olan genel kavramları tanıtacağım.
Karşı taraf
Bu bölümdeki bilgilerle oluşturulan dosyaların her ortamda rahatça okunabileceklerini düşünmeyin. Benzer şekilde, başka ortamlarda oluşturulmuş dosyaların da bu bölümdeki bilgilerle açılabileceklerini beklemeyin.
Bir dosya oluşturup içine bilgiler yazmak, o dosyanın başka bir ortamda açılıp okunması için yeterli olmayabilir. Dosyayı yazan tarafla okuyan tarafın belirli konularda anlaşmış olmaları gerekir. Örneğin dosyaya char[] olarak yazılmış olan bir bilginin wchar[] olarak okunması yanlış sonuç doğurur.
İki ders sonra Unicode dosyaların en başlarına yazılan BOM belirteçlerini öğreneceksiniz. O belirteçler de bu bölümdeki File olanaklarını yanıltacaklardır.
Bu bölümde göreceğiniz File olanaklarını, şu durumlarda kullanışlı olacaklarını düşünerek öğrenin:
- yalnızca programınız tarafından yazılacak olan ve yine programınız tarafından okunacak olan dosyalar; örneğin programınızın ayarlarının ve verilerinin saklandığı dosyalar
- BOM'suz UTF-8 okuyabilen bir ortama gönderilen UTF-8 dosyaları; eğer karşı taraf BOM'suz UTF-8 dosyaları doğru olarak açabiliyorsa, bu bölümdeki olanakları kullanabilirsiniz
- başka bir ortamdan gelen BOM'suz UTF-8 dosyaları; bu bölümdeki olanakları kullanarak bu tür dosyaları doğru olarak açabilirsiniz
Dosya erişim hakları
İşletim sistemi, dosyaları programlara çeşitli erişim haklarıyla sunar. Erişim hakları hem performans, hem de dosya sağlığı açısından önemlidir.
Konu dosyadan okumak olunca; aynı dosyadan okumak isteyen birden fazla programa aynı anda okuma izni verilmesi, programlar birbirlerini beklemeyecekleri için hız kazancı sağlar. Öte yandan, konu dosyaya yazmak olunca; dosyanın içeriğinin tutarlılığı açısından dosyaya belirli bir anda ancak tek bir programın yazmasına izin verilmelidir; yoksa iki programın birbirlerinden habersiz olarak yazmaları sonucunda dosyanın içeriği tutarsız hale gelebilir.
D dosyaları, erişim haklarından temelde iki tanesi ile ilgilidir: okuma ve yazma erişimi.
Dosya açmak
Programın standart giriş ve çıkış akımları olan din ve dout, program başladığında zaten açılmış ve kullanıma hazır olarak gelirler; onları kullanmaya başlamadan önce özel bir işlem yapmamız gerekmez.
Dosyaların ise diskteki isimleri ve istenen erişim hakları bildirilerek program tarafından açılmaları gerekir.
Dosya kapatmak
Açılan dosyaların mutlaka kapatılmaları da gerekir; ancak, File nesneleri kendileri sonlanırken dosyalarını da kapattıkları için, normalde bu işin programda açıkça yapılması gerekmez. Dosya, File nesnesinin içinde bulunduğu kapsamdan çıkılırken kendiliğinden kapatılır. Örnek:
if (bir_koşul) { // dosya burada oluşturulmuş ve kullanılmış olsun // ... } // ← dosya bu kapsamdan çıkılırken otomatik olarak kapatılır
Bazen, aynı dosyanın aynı kapsam içinde ama değişik erişim haklarıyla açılması gerekebilir. Örneğin başlangıçta yazma erişimi ile açılarak içine bilgi yazılmış olan bir dosya; yine aynı kapsam içinde, ama bu sefer okuma erişimi ile açılabilir. Böyle bir durumda, dosyayı önce kapatmak, sonra yeni erişim hakkıyla tekrar açmak gerekir.
Dosyaya yazmak ve dosyadan okumak
Dosyalar da karakter akımları oldukları için, alışık olduğunuz writeln ve readf gibi işlevleri onlarla da kullanabilirsiniz.
Dosya sonu: eof()
Bir dosyadan okurken dosyanın sonuna gelinip gelinmediği, "dosya sonu" anlamına gelen "end of file"ın kısaltması olan eof() üye işleviyle denetlenir. Bu işlev, dosya sonuna gelindiğinde true döndürür.
Klasör işlemleri için std.file modülü
Klasör işlemleri ile ilgili olan std.file modülünün Ddili Wiki'deki sayfasında işinize yarayacak işlevler bulabilirsiniz. Örneğin exists, belirtilen isimde bir dosyanın klasörde zaten bulunup bulunmadığını bildirir:
if (exists(dosya_ismi)) { // dosya mevcut } else { // dosya mevcut değil }
std.stdio.File yapısı
Bu yapının iyi yanları; erişim belirteçlerinin C dilindekilerle aynı olması, ve yapının şimdiye kadar kullandığımız std.stdio modülünde zaten var olmasıdır.
Kötü tarafı, BOM belirteci kullanan Unicode kodlamalarıyla doğru olarak çalışamamasıdır. Bu yapıyı ya bütünüyle ASCII dosyalarla kullanabilirsiniz, ya da BOM içermeyen UTF-8 dosyalarıyla.
Erişim belirteçleri C'nin standart fopen işlevinin kullandıkları ile aynıdır:
| Belirteç | Anlamı |
|---|---|
| r | okuma erişimi dosya, başından okunacak şekilde hazırlanır |
| r+ | okuma ve yazma erişimi dosya, başından okunacak ve başına yazılacak şekilde hazırlanır |
| w | yazma erişimi dosya yoksa: boş olarak oluşturulur dosya zaten varsa: içi boşaltılır |
| w+ | okuma ve yazma erişimi dosya yoksa: boş olarak oluşturulur dosya zaten varsa: içi boşaltılır |
| a | sonuna yazma erişimi dosya yoksa: boş olarak oluşturulur dosya zaten varsa: içeriği korunur ve sonuna yazılacak şekilde hazırlanır |
| a+ | okuma ve sonuna yazma erişimi dosya yoksa: boş olarak oluşturulur dosya zaten varsa: içeriği korunur; başından okunacak ve sonuna yazılacak şekilde hazırlanır |
Yukarıdaki erişim haklarının "rb" gibi sonuna 'b' karakteri gelenleri de vardır. O durumda, dosyaya yazma ve dosyadan okuma işlemleri sırasında karakter dönüşümleri yapılmaz. Bazı özel kodların değişik ortamlardaki alt düzey gösterimleriyle ilgili olan bu konuyu bu derste anlatmayacağım.
Bu yapının belgesini std.stdio wiki sayfasında bulabilirsiniz.
std.stdio.File ile yazma örneği
import std.stdio; void main() { File dosya = File("ogrenci_bilgisi", "w"); dosya.writeln("İsim : ", "Zafer"); dosya.writeln("Numara: ", 123); dosya.writeln("Sınıf : ", "1A"); }
Dizgiler dersinden hatırlayacağınız gibi, "ogrenci_bilgisi" gibi bir dizginin türü string'dir ve değişmezdir. Yani File, dosya ismini ve erişim hakkını string türü olarak kabul eder. Bu yüzden, yine dizgiler bölümünden hatırlayacağınız gibi, File'ı örneğin char[] türünde bir dizgi ile kuramazsınız; kurmanız gerektiğinde o dizginin .idup niteliğini çağırmanız gerekir. Bu bilgiyi problemlerden birisinde hatırlamanız gerekecek.
Yukarıdaki program, çalıştırıldığı klasör içinde ismi ogrenci_bilgisi olan bir dosya oluşturur.
Not: Dosya ismi olarak dosya sisteminin izin verdiği her karakteri kullanabilirsiniz. Ben bu derslerde dosya isimlerinde yalnızca ASCII harfler kullanacağım.
std.stdio.File ile okuma örneği
import std.stdio; void main() { File dosya = File("ogrenci_bilgisi", "r"); while (!dosya.eof()) { string satır = dosya.readln(); writeln("Okuduğum satır -> |", satır); } }
Yukarıdaki program, çalıştırıldığı klasör içindeki ogrenci_bilgisi isimli dosyanın içindeki satırları başından sonuna kadar okur ve standart çıkışa yazdırır.
std.stream.File sınıfı
Bu sınıfın iyi yanı, kullanımının din'in ve dout'un kullanımlarından hiçbir farkının bulunmamasıdır. Bu tür dosyaları kullanırken dosyadan yalnızca satır satır değil, değişken değişken de okuyabilirsiniz.
Bu sınıfı da BOM belirteciyle başlayan Unicode dosyalarıyla doğrudan kullanamazsınız. Ancak, iki ders sonra anlatılacağı gibi, File nesneniz ile yeni bir EndianStream nesnesi oluşturursanız, o yeni nesneyle BOM'lu dosyaları doğru olarak okuyabilirsiniz.
Erişim belirteçleri, daha sonra öğreneceğimiz enum değerler olarak tanımlanmıştır:
| Belirteç | Anlamı |
|---|---|
| FileMode.In | okuma erişimi dosya, başından okunacak şekilde hazırlanır |
| FileMode.Out | yazma erişimi dosya yoksa: boş olarak oluşturulur dosya zaten varsa: başına yazılacak şekilde hazırlanır |
| FileMode.OutNew | yazma erişimi dosya yoksa: boş olarak oluşturulur dosya zaten varsa: içi boşaltılır |
| FileMode.Append | okuma ve sonuna yazma erişimi dosya yoksa: boş olarak oluşturulur dosya zaten varsa: içeriği korunur; başından okunacak ve sonuna yazılacak şekilde hazırlanır |
Bu sınıfın belgesini std.stream wiki sayfasında bulabilirsiniz.
std.stream.File ile yazma örneği
import std.stream; void main() { File dosya = new File("ogrenci_bilgisi", FileMode.OutNew); dosya.writefln("İsim : ", "Zafer"); dosya.writefln("Numara: ", 123); dosya.writefln("Sınıf : ", "1A"); }
std.stdio modülündeki File'dan iki farkı olduğuna dikkat edin: Sınıf nesnesi olduğu için new anahtar sözcüğü ile oluşturulur, ve üye işlevin ismi writeln değil, writefln'dir.
std.stream.File ile okuma örneği
File'ın "müsait" anlamına gelen available niteliği, dosyada hâlâ okunacak karakter kalıp kalmadığını, yani okumaya müsait olup olmadığını bildirir.
import std.cstream; import std.stream; void main() { File dosya = new File("ogrenci_bilgisi", FileMode.In); while (dosya.available) { char[] satır = dosya.readLine(); dout.writefln("Okuduğum satır -> |", satır); } }
std.stdio ile std.stream karışıklığı
Ben bu programda dout.writefln kullandım. Onun yerine daha önceki programlarda gördüğümüz writeln'ı kullanmak isteseydik, writeln'ın içinde bulunduğu std.stdio modülünü de eklememiz gerekirdi. Ne yazık ki öyle yapıldığında bir derleme hatası oluşur:
import std.stdio; // ← bir File bu modülden gelir import std.stream; // ← bir File da bu modülden... void main() { File dosya = new File("ogrenci_bilgisi", FileMode.In); // ^^^^ Derleme HATASI: hangi File olduğu belli değil!
Derleme hatasının nedeni, bizim kısaca File dediğimiz olanağın std.stdio.File ve std.stream.File'dan hangisi olduğunun açık olmamasıdır; derleyici buna kendisi karar veremez. Hem std.stdio modülünü eklemek, hem de std.stream.File sınıfını kullanmak gerektiğinde türün ismini tam olarak yazmamız gerekir:
import std.stdio; import std.stream; void main() { std.stream.File dosya = new std.stream.File("ogrenci_bilgisi", FileMode.In); while (dosya.available) { char[] satır = dosya.readLine(); writeln("Okuduğum satır -> |", satır); } }
Bu programda satırları çıkışa yazdırmak için std.cstream modülündeki dout.writefln değil, std.stdio modülündeki writeln kullanılmıştır.
File türünün ismini uzun olarak yazmak hem daha çok iş gerektirir, hem de hatalı yazma olasılığını arttırır. Bir sonraki derste göreceğiniz auto anahtar sözcüğü ile tür isimlerini yazmanın çok daha kısa olduğunu göreceksiniz.
Problemler
- Yazacağınız program kullanıcıdan bir dosya ismi alsın, o dosyanın içindeki boş olmayan bütün satırları, dosyanın ismine
.bakeklenmiş başka bir dosyaya yazsın. Örneğin, verilen dosyanın ismideneme.txtise, boş olmayan satırlarınıdeneme.txt.bakdosyasına yazsın. std.streammodülünün belgelerinde görüldüğü gibi, File'ın kurucu işlevi, dosya isministringtüründe alır. Eğer elinizde değişebilen türde bir dizgi varsa, örneğin birchar[]varsa, dosya ismi olarak onun.idupile alınmış bir kopyasını kullanmanız gerekecektir- Satırın boş olup olmadığını
.lengthniteliğinin değerine bakarak anlayabilirsiniz - Öğrenci bilgileri tutulan bir dosya olduğunu düşünelim. Dosyanın satırlarında önce öğrencinin adı, soyadı, ondan sonra da iki tane ders notu bulunuyor olsun (bu dosya düzeninde öğrencilerin orta isimleri bulunmasın):
İşinize yarayacak bilgiler:
Demir Çelikçi 80 95 Çiçek Böcekçi 97 90
Ben örnek olarak iki öğrenci yazdım. Sizin programınız dosyada belirsiz sayıda öğrenci olduğunda da doğru çalışsın: öğrenci kayıtlarını dosyadan satır satır okusun ve öğrencilerin yalnızca ön isimlerini ve ders ortalamalarını, virgülden sonrasını da gösterecek şekilde yazdırsın:
Demir: 87.5 Çiçek: 93.5
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları