Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

13.3 Binärdateien

13.3 Binärdateien

Gerade wenn man wichtige Informationen, wie Strukturen und Einstellungen, speichern und laden möchte, sind Textdateien nicht der beste Weg (mal abgesehen von INI oder XML Dateien). Wie eben erwähnt, kann man nicht davon ausgehen, auch das zu finden, was man erwartet, weil andere die Datei händisch manipuliert haben könnten. Dies ist zwar bei Binärdateien prinzipiell auch möglich, aber nicht so verlockend wie bei Textdateien.

Zudem ergeben sich aus der Verwendung von Binärdateien weitere Vorteile. Zum einen können in ihr ganze Strukturen abgelegt bzw. aus ihr entnommen werden. Daraus ergibt sich wiederum, dass man zumindest grundlegend erst einmal testen kann, ob die Datei konsistent ist. Dies könnte man so anstellen, dass man prüft, ob die Dateigröße ein vielfaches des Speicherbedarfs des zu erwarteten Typs ist (Ist der einzulesende Typ 2 KB und die Datei 11 KB groß weiß man schon vor dem öffnen der Datei, dass etwas faul ist).

Der Inhalt der Datei sieht dann so aus, wie man die Variablen im Hauptspeicher des Rechners vorfinden würde. Ein Integer würde dann also genau vier Byte (Zeichen) einnehmen. Für kleine werte (<1000) erscheint dies zwar als Speicherverschwendung, dafür spart man aber Platz bei großen werden. In der Regel spart man durchschnittlich, zumal man kleinere Werte in kleineren Datentypen unterbringen kann.

Zum Seitenanfang
Zum Inhaltsverzeichnis

13.3.1 Die Funktionen fread() und fwrite()

13.3.1 Die Funktionen fread() und fwrite()

Das Öffnen und Schließen von Binärdateien funktioniert grundlegend genauso wie mit Textdateien, nur mit dem Unterschied, dass man beim Öffnen einen speziellen Modus angibt.

Mit der Funktion "fread" kann man nun aus der Datei lesen und mit "fwrite" in sie schreiben. Den Funktionen wird jetzt, neben der Variable mit dem Inhalt bzw. der Variable in welcher der Inhalt kommen soll und dem Datei-Handle, noch zwei Weitere Werte übergeben, welche angeben, wie groß der einzulesende Wert ist und wie viele davon einzulesen sind. So ist es theoretisch möglich, gleich ein ganzes Array mit einem Schlag zu speichern bzw. es auszulesen.

Im folgenden Beispiel werde ich durch den Anwender Personenangaben einlesen und jene in einer Struktur speichern. Jenes wird dann in eine Datei gespeichert und anschließend wieder ausgelesen. Zunächst implementiere ich eine Struktur, welche verschiedene Personeninformationen aufnehmen kann.

 1
 2
 3
 4
 5
 6
					
struct SPerson {
	char	astrVorname[80];
	char	astrNachname[80];
	int	iAlter;
	float	fGewicht;
};
					

Dieser Typ soll verwendet werden. Er hat eine Größe von 168 Byte. Die Dateigrößen werden also immer ein Vielfaches dieser Zahl sein. Kümmern wir uns nun um die Eingabe der Werte und das Abspeichern in die Binärdatei.

 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
					
char*	pcstrFileName		= "C:\\Test.bin";
bool	bGoOn			= true;
FILE*	hFile			= fopen(pcstrFileName, "w+b");

// Wenn die Datei erstellt werden konnte
if (hFile != NULL) {
	printf("== Eingabe ========================\n");

	// Solange Personen hinzugefügt werden sollen
	while (bGoOn) {
		SPerson sPerson;
		char	cSign;

		printf("Vorname:  ");
		fflush(stdin);
		scanf("%s", &(sPerson.astrVorname));
		
		printf("Nachname: ");
		fflush(stdin);
		scanf("%s", &(sPerson.astrNachname));
		
		printf("Alter:    ");
		scanf("%i", &(sPerson.iAlter));
		
		printf("Gewicht   ");
		scanf("%f", &(sPerson.fGewicht));

		try {
			fwrite(&sPerson, sizeof(SPerson), 1, hFile);
		} catch(...) {
			printf("Beim Speichern ist ein Fehler aufgetreten!\n");
		} // end of try
		
		printf("\n\nNoch eine Person einlesen? (j/n)");
		fflush(stdin);
		scanf("%c", &cSign);
		bGoOn		= (cSign == 'j' || cSign == 'J');
	} // end of while

	fclose(hFile);
} // end of if
					

Als nächstes folgen das Auslesen der eben erstellten Datei und die Ausgabe auf dem Bildschirm. Dabei werde ich dieselben Variablen benutzen, wie eben. Besonders auffällig ist nun das doppelte Prüfen auf "end of file". Sie können sich ja mal überlegen, was passiert, wenn man die innere Prüfung weg lässt.

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
					
hFile			= fopen(pcstrFileName, "rb");

// Wenn die Datei geöffnet werden konnte
if (hFile != NULL) {
	printf("== Ausgabe ========================\n\n");

	// Solange es noch etwas zum auslesen gibt
	while (!feof(hFile)) {
		SPerson sPerson;

		try {
			fread(&sPerson, sizeof(SPerson), 1, hFile);

			// Nur wenn noch was eingelesen werden konnte
			if (!feof(hFile)) {
				printf("Vorname:  %s\n", sPerson.astrVorname);
				printf("Nachname: %s\n", sPerson.astrNachname);
				printf("Alter:    %i\n", sPerson.iAlter);
				printf("Gewicht:  %g\n\n", sPerson.fGewicht);
			} // end of if
		} catch (...) {
			printf("Beim Einlesen ist ein Fehler aufgetreten!\n");
		} // end of try
	} // end of while

	fclose(hFile);
} // end of if						
					

So könnte nun eine Ausgabe des Programms aussehen.

== Eingabe ========================
Vorname:  Hans
Nachname: Müller
Alter:    20
Gewicht   80.5

Noch eine Person einlesen? (j/n)j

Vorname:  Paul
Nachname: Meier
Alter:    21
Gewicht   75.25

Noch eine Person einlesen? (j/n)n

== Ausgabe ========================
Vorname:  Hans
Nachname: Müller
Alter:    20
Gewicht:  80.5

Vorname:  Paul
Nachname: Meier
Alter:    21
Gewicht:  75.25
		
Zum Seitenanfang
Zum Inhaltsverzeichnis

13.3.2 Binärdateien mit Streams

13.3.2 Binärdateien mit Streams

Nun werde ich noch abschließend zeigen, wie man eben gezeigten Quelltext mit Streams realisieren kann. Ich werde dabei den gleichen Datentyp benutzen wie eben.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
					
char*		pcstrFileName		= "C:\\Test.bin";
bool		bGoOn			= true;

std::ofstream	oOutFileStream(pcstrFileName, std::ios_base::binary);

// Wenn die Dateien geöffnet werden konnten
if (oOutFileStream.is_open()) {
	try {
		printf("== Eingabe ========================\n");

		// Solange Personen hinzugefügt werden sollen
		while (bGoOn) {
			SPerson sPerson;
			char	cSign;

			printf("Vorname:  ");
			fflush(stdin);
			scanf("%s", &(sPerson.astrVorname));
			
			printf("Nachname: ");
			fflush(stdin);
			scanf("%s", &(sPerson.astrNachname));
			
			printf("Alter:    ");
			scanf("%i", &(sPerson.iAlter));
			
			printf("Gewicht   ");
			scanf("%f", &(sPerson.fGewicht));

			try {
				oOutFileStream.write((char*)&sPerson, sizeof(SPerson));
			} catch(...) {
				printf("Beim Speichern ist ein Fehler aufgetreten!\n");
			} // end of try

			printf("\n\nNoch eine Person einlesen? (j/n)");
			fflush(stdin);
			scanf("%c", &cSign);

			bGoOn		= (cSign == 'j' || cSign == 'J');
		} // end of while
	} catch (...) {
		printf("Es ist ein Fehler aufgetreten!\n");
	} // end of try
} // end of if

oOutFileStream.close();

std::ifstream	oInFileStream(pcstrFileName, std::ios_base::binary);

// Wenn die Dateien geöffnet werden konnten
if (oInFileStream.is_open()) {
	try {
		printf("== Ausgabe ========================\n\n");

		// Solange es noch etwas zum auslesen gibt
		while (!oInFileStream.eof()) {
			SPerson sPerson;

			try {
				oInFileStream.read((char*)&sPerson, sizeof(SPerson));

				// Nur wenn noch was eingelesen werden konnte
				if (!oInFileStream.eof()) {
					printf("Vorname:  %s\n", sPerson.astrVorname);
					printf("Nachname: %s\n", sPerson.astrNachname);
					printf("Alter:    %i\n", sPerson.iAlter);
					printf("Gewicht:  %g\n\n", sPerson.fGewicht);
				} // end of if
			} catch (...) {
				printf("Beim Einlesen ist ein Fehler aufgetreten!\n");
			} // end of try
		} // end of while
	} catch (...) {
		printf("Es ist ein Fehler aufgetreten!\n");
	} // end of try
} // end of if
					

Die Ausgabe sieht wieder genauso aus, wie vorhin, aber vom Quelltext her hat sich einiges geändert. Es geht schon in Zeile 4 los. Genauso wie bei den Textdateien, habe ich ein Streamobjekt erzeugt, jedoch mit einem zusätzlichen Parameter, um zu signalisieren, dass die zu bearbeitende Datei eine Binärdatei ist.

In Zeile 22 wird jetzt das schreiben in die Datei angestoßen. Allerdings benutze ich dieses mal nicht den Serialisierungsoperator, da jener nur mit reinem Text funktioniert bzw. wenn man mit Textdateien arbeitet. Sehr merkwürdig ist hier, dass ich die zu schreibende Variable in einen char Pointer caste. Dies ist notwendig und hat folgenden Hintergrund. Der Stream schreibt und ließt immer Byteweise. Jeder Datentyp ist ja ohne Rest in n Bytes teilbar (auch für ein bool wird intern ein Byte Speicher benötigt). Diese eingelesenen Bytes, werden also in den RAM bzw. die Datei geschrieben, ohne wirkliche Chars zu sein (ein Char ist nun mal ein elementarer Datentyp, der genau ein Byte groß ist). Da die Methode "write" ein Pointer erwartet, muss ich nicht die Struktur selber, sondern seine Adresse übergeben, was das "&" erklärt.

In Zeile 37 schließe ich jetzt händisch die Datei, da ich sie sonst in Zeile 38 nicht wieder mit dem anderen Streamobjekt öffnen kann (auch hier wieder im Binärmodus).

In Zeile 51 wird das Lesen aus der Datei veranlasst und wie eben schon+ angedeutet, ist auch hier wieder ein Casten auf einen Char-Pointer notwendig und man kann ebenfalls nicht die Serialisierungsoperatoren verwenden.

Wenn man dieses Beispiel adaptiert, kann man auch Textdateien händisch kopieren und bekommt dann auch alle Sonderzeichen, wie Leerzeichen und Zeilenumbrüche, mit. Als Variable muss man lediglich ein einzelnes Char benutzen (niemals eine Zeichenkette, da sonst am Ende der Datei, unter Umständen, etwas fehlen könnte).

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012