ein Kapitel zurück                                           ein Kapitel weiter

Diese beiden Funktionen kann man nicht so genau einordnen in ein Thema der Dateifunktionen. Weder in den in diesem Kapitel vorgestellten Standartdateifunktionen (High Level) noch in dem nächsten Kapitel den Low-Level Funktionen. Den mit fread und fwrite wird eine Datei weder als strukturierte Textdatei noch als unformatierter Bytestrom betrachtet. Die Dateien werden im Binärmodus geöffnet und haben eine feste Satzstruktur. Das heißt fread und fwrite machen nicht anderes als Binäres Lesen und Schreiben ganzer Blöcke. Wozu? Nun für ganze Blöcke von binäre Daten ist weder zum lesen fgets (wegen besonderer Bedeutung der Zeichen '\0' und '\n') noch ist es sinnvoll Daten Zeichen für Zeichen mit getc einzulesen.

Hierzu nun erst mal der Syntax der beiden Funktionen...

size_t fread(void *puffer, size_t blockgroesse, size_t blockzahl, FILE *datei);
size_t fwrite(const void *puffer,size_t blockgroesse, size_t blockzahl, FILE *datei);

size_t ist wieder ein Primitiver Systemdatentyp für eine Größe von Objekten. Beginnen wir zuerst mit der Erklärung von fread. Lesen sie es Aufmerksam durch den es ist simpler als es aussieht.

Also fread liest blockzahl Objekte von denen jedes Objekt die Größe von blockgroesse Bytes hat aus der Datei FILE *datei die sie geöffnet haben in die Adresse von puffer. In puffer muss natürlich auch dementsprechend Platz sein. Dafür sind sie verantwortlich.

Das Ganze wollen wir uns mal Bildlich ansehen.....

fread


Jetzt wollen wir das ganze mal nicht ganz so theoretisch halten. Mal ein Beispiel: Wir haben eine Datei Namens "wert.dat" und in dieser Datei stehen Integerwerte. Jetzt wollen wir aus dieser Datei die ersten 10 Werte auslesen und auf dem Bildschirm ausgeben. Unser Programm wird in etwa so aussehen...

.....
int puffer[10];

FILE *quelle;
.....
if((quelle=fopen("wert.dat","r+b")) == NULL)
.....
fread(&puffer, sizeof(int), 10, quelle);

for(i=0; i<10; i++)
     printf("Wert %d = %d\n",i,wert[i]);
..... 

So jetzt interpretieren wir folgende Zeile nochmals wie wir es oben getan haben...

fread(&puffer, sizeof(int), 10, quelle);

Also fread ließt 10 Objekte von denen jedes Objekt die Größe von sizeof(int) Bytes hat aus der Datei FILE *quelle die sie geöffnet haben in die Adresse von puffer. In puffer muss natürlich auch dementsprechend Platz sein. Dafür sind sie verantwortlich.

Natürlich ist das immer noch allzu theoretisch doch gehen wir es lieber langsam an. Ich schätze mal was Ihnen eine wenig undurchsichtig erscheint dürfte der Parameter in fread und fwrite void *puffer sein. Sie haben den void eigentlich bisher als leeren Datentyp kennen gelernt. Nun void * lässt sich auch recht einfach erklären. void * ist ein untypisierter Zeiger (typenloser Zeiger). Das heißt für sie in der Praxis das sie diesem Parameter jeden beliebigen Zeiger übergeben können. Ob nun int, double, char oder struct..... In unserm Beispiel oben was ein Array vom Typ int. Keine sorge ich liefere schon noch einige lauffähige Beispiele zu diesem Thema. Jetzt kommen wir zum Trockenschwimmen mit fwrite.

Schauen wir uns nochmals den Syntax an bevor sie nach oben scrollen müssen...

size_t fwrite(const void *puffer,size_t blockgroesse, size_t blockzahl, FILE *datei);

Nun wieder zu Erklärung die wie sie sich denken könne ähnlich ist zu fread.

Mit fwrite schreiben blockzahl Objekte von denen jedes eine Größe von blockgroesse Bytes hat von der Adresse puffer in die Datei *datei die sie mit fopen geöffnet haben.

Sehen wir uns das ganze wieder Bildlich an....

fwrite


Natürlich heißt das normalerweise das wir in die Datei schreiben auf die unser FILE - Zeiger datei zeigt.

Wieder ein Beispiel wie es in etwa Aussehen könnte...

struct {
          char name[20];
          char vornam[20];
          char wohnort[30];
          int alter;
          int plz;
          char Strasse[30];
          }adressen;

FILE *quelle;

.............

strcpy(adressen.name,"Bill");
strcpy(adressen.vornam,"Clinton");
strcpy(adressen.wohnort,"Washington D.C");
adressen.alter=55;
adressen.plz=232;
strcpy(adressen.Strasse,"7th Street");

...............

if((quelle=fopen("adres.dat","w+b")) == NULL)
..............

fwrite(&adressen, sizeof(adressen), 1, quelle);
..............

Nun wollen wir wieder wie bei fread fwrite interpretieren...

Mit fwrite schreiben 1 Objekt von denen jedes eine Größe von sizeof(adressen) Bytes hat von der Adresse adressen in die Datei quelle die sie mit fopen geöffnet haben.

Nun möchte ich Ihnen auch mal ein Beispiel liefern wie man fread und fwrite in der Praxis anwenden kann. Als Beispiel möchte ich Ihnen ein Adressprogramm zeigen ohne irgendwelche Extras wie Adressen sortieren etc. um nicht vom Thema dieses Kapitels abzulenken. Hier nun ein kleines Beispiel mit vielen anderen bekannten Funktion die wir aus den Vorherigen Kapitel über High-Level Datei I/O kennen gelernt haben...

/*Download:fread.c*/
#include <stdio.h> #include <stdlib.h> struct { char vorname[20]; char nachname[30]; char strasse[30]; char hausnummer[5]; char plz[7]; char ort[30]; char sternzeichen[30]; char alter[3]; }adressen; void speichern() { FILE *save; if((save=fopen("adressen.dat","r+b")) == NULL) { if((save=fopen("adressen.dat","w+b")) == NULL) { fprintf(stderr,"Kann \"adressen.dat\" nicht oeffnen!\n"); exit (0); } } /*FILE - Zeiger save auf das Ende der Datei setzten*/ fseek(save, 0, SEEK_END); /*Wir schreiben eine Adresse ans Ende von "adressen.dat"*/ if(fwrite(&adressen, sizeof(adressen), 1, save) != 1) { fprintf(stderr,"Fehler bei fwrite...!!!\n"); exit (1); } /*Wir geben unseren FILE - Zeiger wieder frei*/ fclose(save); } void ausgabe() { FILE *output; if((output=fopen("adressen.dat","r+b")) == NULL) { fprintf(stderr,"Konnte \"adressen.dat\" nciht oeffnen\n"); exit (2); } /*Wir lesen alle Adressen aus "adressen.dat"*/ while(fread(&adressen, sizeof(adressen), 1, output) ==1) { printf("Vorname...........: %s",adressen.vorname); printf("Nachname..........: %s",adressen.nachname); printf("Strasse...........: %s",adressen.strasse); printf("Hausnummer........: %s",adressen.hausnummer); printf("Postleitzahl......: %s",adressen.plz); printf("Ort...............: %s",adressen.ort); printf("Sternzeichen......: %s",adressen.sternzeichen); printf("Alter.............: %s",adressen.alter); printf("\n\n"); } fclose(output); } void eingabe() { printf("Vorname...........:"); fgets(adressen.vorname, sizeof(adressen.vorname), stdin); printf("Nachname..........:"); fgets(adressen.nachname, sizeof(adressen.nachname),stdin); printf("Strasse...........:"); fgets(adressen.strasse, sizeof(adressen.strasse), stdin); printf("Hausnummer........:"); fgets(adressen.hausnummer, sizeof(adressen.hausnummer), stdin); printf("Postleitzahl......:"); fgets(adressen.plz, sizeof(adressen.plz), stdin); printf("Ort...............:"); fgets(adressen.ort, sizeof(adressen.ort), stdin); printf("Sternzeichen......:"); fgets(adressen.sternzeichen, sizeof(adressen.sternzeichen), stdin); printf("Alter.............:"); fgets(adressen.alter, sizeof(adressen.alter), stdin); speichern(); } int main() { char jn; do{ eingabe(); ausgabe(); fflush(stdin); printf("Weitere Dateien eingeben (j/n): "); scanf("%c",&jn); fflush(stdin); }while(jn != 'n'); return 0; }

Wir haben in unserem Programm eine Struktur mit dem Namen adressen definiert. Aber beginnen wir mit dem Programmablauf. Als erstes wir unsere Funktion eingabe() aufgerufen die eigentlich nicht schwer sein dürfte zu verstehen. An diesem Beispiel können sie auch erkennen wie wir fgets mit Hilfe des sizeof - Operators einsetzten um einen Pufferüberlauf zu verhindern. Am Ende dieser Funktion wird unsere Funktion speichern() aufgerufen. Als erstes öffnen wir falls vorhanden die Datei "Adressen.dat" und falls nicht vorhanden erstellen wir eine neue Datei mit dem Namen mit...

if((save=fopen("adressen.dat","r+b")) == NULL)
   {
     if((save=fopen("adressen.dat","w+b")) == NULL) 

Anschließend setzen wir mit...

fseek(save, 0, SEEK_END);

...unseren FILE - Zeiger save an das Ende der Datei Adressen.dat. Nun schreiben wir den Adresssatz den wir zuvor eingegeben haben mit...

if(fwrite(&adressen, sizeof(adressen), 1, save) != 1)

...in unsere Datei Adressen.dat auf die unser FILE - Zeiger save zeigt. Nochmals die einzelnen Erklärungen der Parameter in fwrite...

&adressen == die Adresse unserer Struktur adressen wo wir die Eingabe geschrieben haben Also der Puffer den wir in die Datei auf die unser FILE-Zeiger save zeigt schreiben wollen.

sizeof(adressen) == die Größe (Blockgröße) in Bytes wieviel wir auf einmal in unsere Datei auf die unser FILE - Zeiger save zeigt vom Puffer schreiben wollen.

1 == Wieviele Blöcke wir von der Größe sizeof(adressen) aus dem Puffer in unsere Datei auf die FILE *save zeigt schreiben wollen.

save == unser FILE - Zeiger mit dem wir zuvor eine Datei geöffnet haben und in dem von der Adresse des Puffers (&adressen) sizeof(adressen) Bytes ein Block geschrieben wird.

Ich hoffe das war jetzt genau genug. Ich habe das ganze in eine if - Anweisung gepackt die eine Fehlerausgabe ausgibt falls weniger wie ein Block geschrieben wird. Im Anschluß wollen wir unsere Datei mit fread Blockweise auslesen und den Inhalt von Adressen.dat auf dem Bildschirm ausgeben. Dies macht unsere Funktion ausgabe(). Wir öffnen wieder eine Datei und geben die Adresse dieser Datei wieder wie immer unserem FILE - Zeiger den wir hier output genannt haben. Somit zeigt output auf dem Anfang der Datei Adressen.dat die wir mittels...

while(fread(&adressen, sizeof(adressen), 1, output) ==1)

...auslesen wollen. Wir lesen hier aus der Datei auf die unser FILE - Zeiger output zeigt jeweils 1 Block von der Größe sizeof(adressen) aus. Dies übergeben wir an die Adresse unserer Struktur adressen. Es wird solange ausgelesen bis kein Block mehr Vorhanden ist. Auch hierzu noch mal ein genaue Erläuterung der einzelnen Parameter in fread...

&adressen == Hierhin wird der Block von der Größe sizeof(adressen) auf die der FILE - Zeiger output zeigt "geschoben".

sizeof(adressen) == die Größe des Blockes den wir aus der Datei lesen und den an die Adresse von adressen geben.

1 == Anzahl der Blöcke die wir lesen wollen

ouput == Unser FILE - Zeiger der auf die Datei zeigt die wir auslesen wollen.

Nun will ich Ihnen noch an Hand eines Beispiels zeigen wie schnell Byteweise Lesen und Schreiben (getc,putc) einer Datei und wie schnell Blockweise Lesen und Schreiben (fread,fwrite) sind. Wir benutzen hierzu Funktionen der Headerdatei time.h in der einige Zeitfunktionen enthalten sind. Anhand diese Beispiels sehen sie das Blockweise Kopieren mindestens doppelt so schnell ist wie Byteweise. Hierzu nun der Quellcode...

/*Download:fread2.c*/
#include <stdio.h> #include <stdlib.h> #include <time.h> #define MAX 1024 void dateien_oeffnen(char *quelle, char *ziel, FILE **ein, FILE **aus) { if((*ein=fopen(quelle,"rb")) == NULL) { fprintf(stderr,"Fehler beim öffnen von %s\n",quelle); exit (0); } if((*aus=fopen(ziel,"wb")) == NULL) { fprintf(stderr,"Fehler beim öffnen von %s\n",ziel); exit (1); } } int main() { int c,n; FILE *quelle,*ziel; char puffer[MAX]; //1024 Bytes = 1KB clock_t start,stop; char dateilesen[20],byteweise[20],blockweise[20]; printf("Welche Datei wollen sie verwenden zum Kopieren : "); scanf("%s",dateilesen); printf("Wie soll die Datei heißen zum Byteweise Kopieren : "); scanf("%s",byteweise); printf("Wie soll die Datei heißen zum Blockweise Kopieren: "); scanf("%s",blockweise); dateien_oeffnen(dateilesen, byteweise, &quelle, &ziel); start = clock(); /*Wir beginnen mit dem kopieren Byte für Byte*/ while((c=getc(quelle)) != EOF) if(putc(c,ziel) == EOF) fprintf(stderr,"Fehler bei putc.....\n"); stop = clock(); fclose(quelle); fclose(ziel); fprintf(stdout, "Kopieren zeichenweise %.2f sek\n", (stop-start) / CLOCKS_PER_SEC); dateien_oeffnen(dateilesen,blockweise, &quelle, &ziel); start = clock(); /*Wir kopieren Blockweise mit fwrite*/ while((n=fread(puffer, MAX, 1, quelle)) >0) fwrite(puffer, MAX, 1, ziel); stop = clock(); fclose(quelle); fclose(ziel); fprintf(stdout,"Kopieren blockweise %.2f sek\n", (stop-start)/CLOCKS_PER_SEC); return 0; }

Ich habe zuerst eine Funktion datei_oeffnen geschrieben da wir das ganze 2 mal machen brauchen wir jeweils nur die Funktion aufzurufen anstatt das ganze zu wiederholen. Die Funktion lässt sich auch gut für weitere Programme anwenden. Das Programm öffnet jeweils eine Datei (Nehmen sie ein etwas größere) und ließt und schreibt zuerst Zeichenweise (Byteweise) und dann Blockweise. Wir geben unseren Variablen start und stop jeweils die Uhrzeit und bekommen dann mittels....

(stop-start)/CLOCKS_PER_SEC

....die Zeit in Sekunden. Versuchen sie dieses mal das Programm ohne Kommentare und Anleitung zu verstehen, da ich fread und fwrite jetzt recht häufig erklärt habe und das Programm eigentlich auch nicht allzu schwer sein dürfte. Das ist so die übliche Anwendung von fread und fwrite.

ein Kapitel zurück          nach oben           ein Kapitel weiter


© 2001,2002 Jürgen Wolf