BİLGİSAYAR PROGRAMLAMA Ders 10: Dosyalama İşlemleri Yrd. Doç. Dr. Altan MESUT Trakya Üniversitesi Bilgisayar Mühendisliği
Dosya Programda kullanılacak verileri veya programda üretilen verileri saklamak için dosyalar kullanılır. Programlama dilleri dosya yaratma, dosya silme, dosyadan bilgi okuma ve dosyaya bilgi ekleme gibi temel işlevler için gerekli komutları içerirler. Dosyalar saklama türüne göre ve kayıt erişim biçimlerine göre aşağıdaki gibi sınıflandırılırlar: Saklama türleri: ikili (binary) ve metin (text) Kayıt erişim biçimleri: sıralı (sequential) ve rastgele (random)
Saklama Türüne Göre Dosyalar Metin (text) türündeki dosyalara bilgi ASCII biçiminde kaydedilir. Bu tip dosyalar Not Defteri (Notepad) gibi bir metin editörleri tarafından okunabilir. İkili (binary) türündeki dosyalara sayısal veriler ASCII formatına dönüştürülmeden doğrudan kaydedilir. Örn: 123 sayısı metin türü dosyaya 00110001, 00110010, 00110011 (49, 50, 51) şeklinde 3 byte olarak kaydedilirken, ikili dosyaya 01111011 şeklinde 1 byte olarak kaydedilir.
Erişim Biçimlerine Göre Dosyalar Sıralı Erişimli (Sequential Access) dosyalarda, kayıtların boyutu sabit değildir. Bu nedenle aradığımız kayda erişmek için tüm kayıtlara tek tek bakmamız gerekir. Rastgele Erişimli (Random Access) dosyalarda, her kayıt sabit bir uzunlukta olmalıdır (Örneğin soyad alanı kayıt deseninde 20 karakter olarak belirlendiyse, o uzunlukta soyad girilmediğinde kalan kısımlar boşluk karakteri ile doldurulur). Bu sayede n. kayda erişmek gerektiğinde, önceki kayıtları atlamak için; (n 1) × Kayıt Boyu kadar dosyada ilerleme seçeneği kullanılabilir.
Sıralı – Rastgele Karşılaştırması Rastgele Erişimli dosyalarda herhangi bir kayda dosyadaki sıra numarası biliniyorsa doğrudan erişilebilir (bu nedenle Doğrudan Erişimli Dosyalar da denir). Fakat dosyalarda gereksiz boşluklar bulunduğu için dosya boyu büyük olur. Sıralı Erişimli dosyalarda gereksiz boşluklar oluşmadığı için dosya boyu rastgele erişimli dosyaya göre daha azdır. Fakat herhangi bir kayda erişmek için dosyanın başından itibaren o kayda kadar olan tüm kayıtların okunması gereklidir. Sıralı Erişim Rastgele Erişim
Dosyalama Komutları C dilinde öncelikle FILE * türünde bir dosya değişkeni yaratılmalıdır. Dosya kullanılmadan önce fopen komutu ile açılır ve yazma/okuma işlemleri bittikten sonra fclose komutu ile kapatılır. Dosyaya veri yazmak için fprintf, fputs, fputc ve fwrite komutları, dosyadan bilgi okumak için fscanf, fgets, fgetc ve fread komutları kullanılır. Not: Başka dosyalama komutları da var ama en çok kullanılanlar bu komutlar olduğu için bunları bilmeniz yeterlidir.
fprintf ve fscanf fprintf(dosya, format string, [değişkenler]); fscanf(dosya, format string, [değişkenler]); Aldıkları ilk parametre olan dosya parametresi dışında kullanımları printf ve scanf ile aynıdır. fprintf ekran yerine dosyaya yazarken, fscanf klavye yerine dosyadan okur. NOT: Burada ve sonraki slaytlarımızda dosya adında verilen parametreler FILE * türünde olan dosya değişkenini ifade etmektedir.
Dosyaya "Merhaba Dünya" yazdıran ve tekrar açıp ilk kelimesini okuyan program #include <stdio.h> main(){ FILE *dosya; char str[100]; dosya = fopen("dosyam.txt", "w"); fprintf(dosya, "Merhaba Dunya"); fclose(dosya); dosya = fopen("dosyam.txt", "r"); fscanf(dosya, "%s", &str); printf("%s", str); } dosyam.txt dosyası yazma (w) kipinde açılır. fopen fonksiyonunun döndüreceği değer dosya verilerinin saklanacağı yerin başlangıç adresidir. Bu değer önceden tanımladığımız dosya değişkenine atanır. Dosya Açma Kipleri: w write (yazma) r read (okuma) a append (ekleme) w+, r+, a+ hem okuma hem yazma, b binary (ikili dosya işlemi için diğerleri ile birlikte kullanılır: “w+b” gibi) fscanf fonksiyonu dosyadan str karakter dizisine sadece bir kelime (Merhaba) aktarır. Aynı satır tekrar yazılırsa sonraki kelimenin (Dunya) aktarılacağı görülebilir.
fputs ve fgets fputs dosyaya bir string yazar fputs(string, dosya); fgets dosyadan bir string okur fgets(string, boyut, dosya); fgets içindeki boyut parametresine verilen değerden bir eksiği kadar karakter dosyadan okunur. Bunun nedeni string sonuna bir de sonlandırma karakteri eklenecek olmasıdır. fputs fonksiyonu fprintf ile benzerdir. Fakat dosya değişkenini ilk değil ikinci parametre olarak alır ve puts fonksiyonunda olduğu gibi sadece düz string yazar (%d gibi bir kullanım ile bir değişkenin değeri yazılacak ise fprintf kullanılmalıdır).
Dosyaya "Merhaba Dünya" yazdıran ve tekrar açıp 11 karakterini okuyan program #include <stdio.h> main(){ FILE *dosya; char str[100]; dosya = fopen("dosyam.txt", "w"); fputs("Merhaba Dunya", dosya); fclose(dosya); dosya = fopen("dosyam.txt", "r"); fgets(str, 12, dosya); printf("%s", str); } Dosyadan 12 değil 11 karakter yani "Merhaba Dun" okunur. Sonuna '\0' karakteri (NULL: boş karakter) eklenir. Bu sonlandırma karakteri sayede alt satırdaki printf fonksiyonu str karakter dizisinin tamamını yani 100 karakteri değil ilk 11 karakterini gösterir.
1 ile 10 arasındaki sayıların karesini dosyaya ekleyen program #include <stdio.h> main(){ FILE *dosya; int i; dosya = fopen("dosya.txt", "w"); for (i=1; i<=10; i++) fprintf(dosya, "%d\n", i*i); fclose(dosya); } Bu programda fprintf yerine fputs kullanılamaz. Çünkü fputs formatlı string kabul etmez.
fgetc ve fputc fgetc dosyadan bir karakter okur karakter = fgetc(dosya); fputc dosyaya bir karakter yazar fputc(karakter, dosya); Dosya kopyalama işleminde dosya1’in sonuna gelene kadar karakter karakter dosya2’ye yazarken aşağıdaki gibi bir kullanım uygulayabilirsiniz: fputc(fgetc(dosya1), dosya2);
Dosyadaki tüm verileri okuyarak ekrana yazdıran program #include <stdio.h> main(){ FILE *d; d = fopen("metin.txt", "r"); while (!feof(d)) printf("%c", fgetc(d)); fclose(d); getchar(); } Dosya okuma kipinde açılarak içindeki bilgiler karakter karakter okunuyor ve ekrana yazdırılıyor. feof fonksiyonu dosya sonuna gelinince true (1) değerini döndürür. while (!feof(..)) = while not end of file (dosya sonuna gelinmediği sürece)
Dosyadaki verileri başka bir dosyaya kopyalayan program #include <stdio.h> main(){ FILE *d1, *d2; char chr; d1 = fopen("metin.txt", "r"); d2 = fopen("metin_kopya.txt", "w"); while (1) { chr = fgetc(d1); if (feof(d1)) break; fputc(chr, d2); } fclose(d1); fclose(d2); while (!feof(d1)) fputc(fgetc(d1), d2); Programdaki while döngüsünü yukarıdaki gibi değiştirirsek, d2'ye eof karakterini de yazar. Yani d2 dosyası, d1 dosyasından 1 byte fazla olur. SORU: fgets fonksiyonunda boyut parametresine dosyanın boyutunu verirsek, dosyadaki tüm verileri döngü kullanmadan tek seferde okuyabilir miydik? CEVAP: Hayır, çünkü fgets ilk ENTER tuşuna kadar okur, yani tek satır okur.
Klavyeden girilenleri alarak dosyaya ekleyen program #include <stdio.h> #include <string.h> main(){ FILE *d; char str[100]; d = fopen("yazi.txt", "w"); do { gets(str); fprintf(d, "%s\n", str); } while (strlen(str)); fclose(d); } Bu program bir satır boş bırakılana kadar girilen tüm satırları dosyaya yazar scanf("%s", str) ilk boşluk karakterine kadar okuduğu için gets kullanıldı. str = "" olduğunda (boş string) strlen sıfır döndürür. while(0) döngüden çıkış anlamına gelir. while (str[0]) gibi bir kullanım da işimizi görürdü. Çünkü boş satırın da ilk karakteri sonlandırma karakteridir (\0) ve onun ASCII değeri de sıfırdır. Bu durumda strlen kullanılmayacağı için #include <string.h> silinebilir.
Yapı (Struct) C’de yapılar dosyalama işlemlerinde bir kayıt deseni oluşturmak amacıyla kullanılabilir. struct Personel { short perno; char ad[15]; char soyad[20]; char tel[15]; float maas; } kayit; Boyutlar 2 15 20 4 Personel yapısından üretilen kayıt değişkeninin boyutu içerdiği 5 farklı değişkenin boyutu kadardır (56 bayt).
Sıralı Erişimli Dosyaya Kayıt Ekleme Buradaki kayit_ekleme() prosedürü main() prosedüründen çağrılmalıdır. Programın başında #include <stdio.h> ve Personel veri yapısı tanımı yer almalıdır. void kayit_ekleme() { struct Personel kayit; FILE *dosya; dosya = fopen("personel.txt", "a"); printf("Personel No : "); scanf("%d", &kayit.perno); printf("Ad : "); scanf("%s", kayit.ad); printf("Soyad : "); scanf("%s", kayit.soyad); printf("Telefon : "); scanf("%s", kayit.tel); printf("Maas : "); scanf("%f", &kayit.maas); fprintf(dosya, "%d %s %s %s %.2f\n", kayit.perno, kayit.ad, kayit.soyad, kayit.tel, kayit.maas); fclose(dosya); } Eğer önceki slaytta olduğu gibi Personel yapısının sonunda kayıt değişkeni tanımlanmazsa, bu şekilde tanımlanabilir.
Sıralı Erişimli Dosyadaki Kayıtları Listeleme void kayit_listeleme() { struct Personel kayit; FILE *dosya; dosya = fopen("personel.txt", "r"); while(1) { fscanf(dosya, "%d %s %s %s %f", &kayit.perno, kayit.ad, kayit.soyad, kayit.tel, &kayit.maas); if(feof(dosya)) break; printf("%d %s %s %s %.2f\n", kayit.perno, kayit.ad, kayit.soyad, kayit.tel, kayit.maas); } fclose(dosya); Daha önce verdiğimiz örnekte olduğu gibi burada da while(1) değil de while(!feof(dosya)) kullanımı yapsaydık, son kaydı iki defa gösterirdi.
Rastgele Erişimli Dosyalama C’de rastgele erişimli dosyalar için ayrı bir dosya açma kipi tanımlanmamıştır. Rastgele erişimli dosyalarda okuma ve yazma için genellikle fread ve fwrite komutları kullanılır. fseek komutu ile istenilen kaydın başına doğrudan erişim sağlanabilir. 11. kaydın başına konumlanmak için, 10. kaydın sonu olan sizeof(kayit)*10 gibi bir ifade kullanılabilir.
Dosyada o anda kaçıncı bayta konumlanıldığını döndüren fonksiyon fseek ve ftell Dosyanın belirli bir yerine konumlanmak için fseek kullanılır: fseek(dosya, 560, SEEK_SET); SEEK_SET ile dosyanın başından … bayt ileriye, SEEK_CUR ile bulunulan konumdan … bayt ileriye, SEEK_END ile dosya sonundan … bayt ileriye konumlanır. Dosyanın neresinde bulunduğumuzu öğrenmek için ftell kullanılır: ftell(dosya); sizeof(kayit)*10 Dosyada o anda kaçıncı bayta konumlanıldığını döndüren fonksiyon
fread ve fwrite C’de bir kayıt deseninin tamamını bir defada dosyaya eklemek için fwrite, dosyadan okumak için ise fread kullanılır: fwrite(&kayit, sizeof(kayit), 1, dosya); fread(&kayit, sizeof(kayit), 1, dosya); İlk parametre fwrite için hangi bellek adresinden veriyi okuyacağı, fread için okuduğunu hangi adrese yazacağı İkinci parametre okuyacağı her veri için boyut bilgisi Üçüncü parametre verilen boyutta kaç adet veri okuyacağı fread NULL değer döndürürse kayıt okuyamamış (muhtemelen dosya sonuna gelmiş) demektir.
Rasgele Erişimli Dosyaya Kayıt Ekleme Dosya uzantısı txt olsa da buradaki dosya metin (ASCII) tabanlı değildir. Dosyayı Not Defteri ile açtığınızda perno ve maas verilerinin nasıl göründüğüne dikkat edin. void kayit_ekleme() { struct Personel kayit; FILE *dosya; dosya = fopen("personel.txt", "a"); printf("Personel No : "); scanf("%d", &kayit.perno); printf("Ad : "); scanf("%s", kayit.ad); printf("Soyad : "); scanf("%s", kayit.soyad); printf("Telefon : "); scanf("%s", kayit.tel); printf("Maas : "); scanf("%f", &kayit.maas); fwrite(&kayit, sizeof(kayit), 1, dosya); fclose(dosya); } Sıralı erişimli dosyalarda kullanılan fprintf'e göre burada kullanılan fwrite komutunun daha kolay kullanımı olduğu görülmektedir.
Rasgele Erişimli Dosyadaki Kayıtları Listeleme void kayit_listeleme() { struct Personel kayit; FILE *dosya; dosya = fopen("personel.txt", "r"); printf("PerNo %-15s %-20s %-15s Maas\n", "Ad", "Soyad", "TelNo"); while(1) { fread(&kayit, sizeof(kayit), 1, dosya); if(feof(dosya)) break; printf("%5d %-15s %-20s %-15s %5.2f\n", kayit.perno, kayit.ad, kayit.soyad, kayit.tel, kayit.maas); } fclose(dosya); fscanf yerine fread kullanımı da daha kısadır. Bu örnekte listeleme öncesi bir başlık ta print edilmekte ve o başlığa uygun hizalamada kayıtlar listelenmektedir.
Sıralı Erişimli Dosyadaki Verileri Rastgele Erişimli Dosyaya Aktarma main() { struct Personel kayit; FILE *d1, *d2; d1 = fopen("personelSeq.txt", "r"); d2 = fopen("personelRnd.txt", "w"); while(1) { fscanf(d1, "%d %s %s %s %f", &kayit.perno, kayit.ad, kayit.soyad, kayit.tel, &kayit.maas); if(feof(d1)) break; fwrite(&kayit, sizeof(kayit), 1, d2); } fclose(d1); fclose(d2); Sıralı erişimli dosyadan kayit altındaki her elemana ayrı ayrı veri aktardık, rastgele erişimliye ise bir defada yazabildik.
Notlar fopen komutu dosyayı bulamaz yada açamazsa NULL değer döndürür : FILE *d; if ((d=fopen("dosya.txt", "w"))==NULL) printf("Dosya acilamadi\n"); Dosyanın İsmini Değiştirme : rename("eski.txt", "yeni.txt"); Dosyayı Silme : remove("deneme.txt");
Ödev 1 Sıralı Erişimli ve Rastgele Erişimli dosyalar ile menülü bir dosyalama programı yazınız. Menüde; "Kayıt Ekleme", "Kayıt Silme", "Kayıt Güncelleme", "Kayıtları Listeleme" ve "Çıkış" seçenekleri olmalıdır. Silme veya Güncelleme seçildiğinde personel numarası istenmeli ve o numara dosyada aranmalıdır. Bulunursa kayıt silinmelidir (yada verilen değerler ile güncellenmelidir).
Ödev 2 Kullanıcının ekleyeceği notları notlar.txt dosyasında sıra ile saklayan bir program yazın. Program çalıştırıldığında dosyanın içinde saklı olan tüm notlar sıra numarası ile birlikte görüntülenecek ve altında "not eklemek için 1’e, not silmek için 2’ye çıkış için ESC’ye basın" mesajı görüntülenecektir. Not Ekleme seçildiğinde "Notunuzu Giriniz" mesajı görüntülenecek ve kullanıcının ekrana yazacağı not ENTER basıldığı anda dosyanın sonuna eklenecektir. Not Silme seçildiğinde "Silmek İstediğiniz Notun Numarasını Giriniz" mesajı görüntülenecek ve kullanıcının seçtiği not dosyadan silinecektir. Ekleme ve silme işlemlerinden sonra ekran temizlenip programın başına dönülmelidir (Dosyanın son hali ekranda gösterilip yine altında "not eklemek için 1’e, not silmek için 2’ye çıkış için ESC’ye basın" mesajı görüntülenmelidir).