Oktober 2018
Der erwachsene Programmierer
Der erwachsene Programmierer beschäftigt sich nicht mit Spielereien, wie virtuelle
Realität, Heim Vernetzung oder wie das heißt. Dass sich die Waschmaschine mit dem
Kühlschrank unterhält, dass Elektronik in Kleidung eingenäht ist, ist verzichtbar
oder unerwünscht. Er hat die Aufgabe riesige Datenmengen in möglichst kurzer Zeit
zuverlässig zu verarbeiten. Früher hat man dieses Problem nicht zuletzt mit
sequentiellen Dateien und prozeduraler Verarbeitung gelöst. Ich glaube dass das
auch heute noch ein günstiger Weg ist, und möchte das an Hand von Beispielen belegen.
Es ist allerdings so, dass moderne Programmiersprachen die Darstellung von binären
Daten in Dateien nicht unterstützen. Sequentielle Dateien können nur aus
druckbaren ASCII Characters bestehen und ein Satz wird durch cr,lf oder beides
abgeschlossen. Die Speicherung von binären Daten in einer solchen Datei ist also
ausgeschlossen, könnten ja binäre Daten auch cr,lf enthalten. Die Abspeicherung
von Daten ist also nur in Datenbanken möglich, vorwiegend relational.
Sicher ist das im Interesse der Hardwarehersteller, da sie dann mehr und leistungs-
fähigere Harware verkaufen können.
Dickkopf der ich bin, habe ich mich mit dieser Situation nicht zufrieden gegeben
und habe selbst ein Dateisystem nach meinen Vorstellungen geschrieben.
Ich habe dazu die Programmiersprache C verwendet, die ist zwar auch schon betagt,
aber sie hat sich über C++ und C# ins 21. Jahrhundert hinüber gerettet.
Jeder Satz besteht aus einem Längenfeld int und den beliebigen Daten. EOF wird
durch -1 im Längenfeld dargestellt (durch ffffffff durch High-Values). Die Datei
hat einen Vorsatz von 38 Byte, ohne Längenfeld, der natürlich nach belieben
modifiziert oder weggelassen werden kann. Unbedingt brauche ich die Features
struct und union. Wirklich moderne Programmiersprachen bieten diese Möglichkeiten
nicht mehr, schlau.
Mein Sequentielles Datei Programm:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
FILE * sequopen(FILE *fp, char fn[], char rwa[]);
// da habe ich lange gebraucht, bis ich auf FILE * gekommen bin
int sequread(FILE *fp, char satz[]);
int sequwrite(FILE *fp, char satz[], int lng_s);
int sequclose(FILE *fp, char rwa[]);
int main(int argc, char *argv[]) {
// ich weiss dass man strukturen auch direkt an funktionen übergeben kann
// aber das ist mir zu kompliziert
union {
char satz[100];
struct {
char sa[4];
char name[30];
char adresse[50];
} stamm;
struct {
char sa[4];
int saldo;
int umsatz;
} bewegung;
} un;
if (argc!=3) {
printf("Bitte Aufruf ./Programm file mode\n");
// mode r,w,a lesen, schreiben, hinzufügen
return 0;
};
FILE *fp;
fp=sequopen(fp,argv[1],argv[2]);
strcpy(un.stamm.sa,"A100");
strcpy(un.stamm.name,"Franz Teuxelsieder");
strcpy(un.stamm.adresse,"Wuzelwang am Wuzel");
if (argv[2][0]!='r'){
sequwrite(fp,un.satz,sizeof(un.stamm));
};
strcpy(un.bewegung.sa,"B100");
un.bewegung.saldo=2;
un.bewegung.umsatz=20000;
if (argv[2][0]!='r'){
sequwrite(fp,un.satz,sizeof(un.bewegung));
};
if (argv[2][0]=='r') {
int lng=sequread(fp,un.satz);
char sa[4];
while(lng != -1){
strncpy(sa,un.satz,4);
printf ("satzart laenge %s %d\n",sa,lng);
lng=sequread(fp,un.satz);
};
};
sequclose(fp,argv[2]);
return 0;
}
FILE * sequopen(FILE *fp, char fn[], char rwa[]) {
union {
int l_satz;
char la[4];
} lng;
union {
char satz[38];
struct {
char sa[4];
char datum[8];
char zeit[6];
char kurz_bez[20];
} vorsatz;
} un;
char datum[9],zeit[7];
// printf ("vor open\n");
fp=fopen(fn,rwa);
if (rwa[0]=='w') {
memcpy(un.vorsatz.sa,"VORS",4);
time_t t = time(NULL);
struct tm tm = *localtime(&t);
sprintf(datum,"%d%02d%02d",tm.tm_year+1900,tm.tm_mon+1,tm.tm_mday);
sprintf(zeit,"%02d%02d%02d",tm.tm_hour,tm.tm_min,tm.tm_sec);
memcpy(un.vorsatz.datum,datum,8);
memcpy(un.vorsatz.zeit,zeit,6);
memcpy(un.vorsatz.kurz_bez,"Dateiformat Groeger",20);
fwrite(un.satz,1,sizeof(un.vorsatz),fp);
};
if (rwa[0]=='a') {
if (fseek(fp,0,SEEK_SET)!=0) {
printf("fseek an den Anfang gescheitert\n");
};
fclose(fp);
fp=fopen(fn,"r+");
// r+ habe ich nach zahlreichen Tests gefunden, scheinbar funktioniert
// nur damit das fseek -4 das es erlaubt die eof Marke zu überschreiben
fread(un.satz,1,sizeof(un.vorsatz),fp);
// Vorsatz überprüfen
if (memcmp(un.vorsatz.sa,"VORS",4)!=0) {
printf("Abort Vorsatz nicht korrekt %s\n",un.vorsatz.sa);
abort();
};
if (fseek(fp,-4,SEEK_END)!=0){
printf("fseek gescheitert\n");
};
};
if (rwa[0]=='r') {
fread(un.satz,1,sizeof(un.vorsatz),fp);
if (memcmp(un.vorsatz.sa,"VORS",4)!=0){
printf("Abort Vorsatz nicht korrekt %s\n",un.vorsatz.sa);
abort();
};
};
return fp;
}
int sequread(FILE *fp, char satz[]) {
union {
int l_satz;
char la[4];} lng;
// länge bzw end of file marke lesen
fread(lng.la,1,4,fp);
if (lng.l_satz!=-1){
fread(satz,1,lng.l_satz,fp);
};
return lng.l_satz;
}
int sequwrite(FILE *fp, char satz[], int lng_s) {
union {
int l_satz;
char la[4];} lng;
lng.l_satz = lng_s;
fwrite(lng.la,1,4,fp);
fwrite(satz,1,lng_s,fp);
return 0;
}
int sequclose(FILE *fp, char rwa[]) {
union {
int l_satz;
char la[4];} lng;
if (rwa[0]!='r') {
// end of file marke: länge = -1
lng.l_satz = -1;
fwrite(lng.la,1,4,fp);
};
fclose(fp);
return 0;
}
Probieren sie es aus und schauen sie sich das Ergebnis mit einem Hex Editor an.
(Ohne Hex Editor wäre das Leben sinnlos).
Das war eine leichte Übung. Spannend wird die sequentelle Verarbeitung erst
durch die Mischlogik. Auch dazu ein Beispiel.
(ich habe konventionelle Dateien verwendet schon um es beim testen leichter zu
haben).
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char *argv[]) {
// for (int i=0;i
// };
union {
char str[6];
struct {
char eof;
char zahl[5];
} s;
} un1;
union {
char str[6];
struct {
char eof;
char zahl[5];
} s;
} un2;
int sz1=0,sz2=0;
char zwi1[5],zwi2[5];
un1.s.eof='0';
un2.s.eof='0';
FILE *p1,*p2,*pa;
p1=fopen("xxx","r");
p2=fopen("yyy","r");
pa=fopen("zzz","w");
if (fgets(un1.s.zahl,5,p1)==NULL) {
un1.s.eof='1';
}
else {
memcpy(zwi1,un1.s.zahl,5);
sz1=1;
};
if (fgets(un2.s.zahl,5,p2)==NULL) {
un2.s.eof='1';
}
else {
memcpy(zwi2,un2.s.zahl,5);
sz2=1;
};
while(un1.s.eof=='0'||un2.s.eof=='0') {
if (memcmp(un1.str,un2.str,5)<0&&un1.s.eof=='0') {
fputs(un1.s.zahl,pa);
if (fgets(un1.s.zahl,5,p1)==NULL) {
un1.s.eof='1';
}
else {
sz1++;
if (memcmp(zwi1,un1.s.zahl,5)>0){
printf ("Sortierfehler Datei 1 %d\n",sz1);
abort();
};
};
memcpy(zwi1,un1.s.zahl,5);
};
if (memcmp(un1.str,un2.str,5)>=0&&un2.s.eof=='0') {
fputs(un2.s.zahl,pa);
if (fgets(un2.s.zahl,5,p2)==NULL) {
un2.s.eof='1';
}
else {
sz2++;
if (memcmp(zwi2,un2.s.zahl,5)>0){
printf ("Sortierfehler Datei 2 %d\n",sz2);
abort();
};
};
memcpy(zwi2,un2.s.zahl,5);
};
};
fclose(p1);fclose(p2);fclose(pa);
return 0;
}
Ich habe testweise Zahlen gemischt und zwar habe ich in der Datei xxx
die ersten 30 Primzahlen 3 stellig gespeichert, in der Datei yyy die ersten 20
geraden Zahlen, natürlich ebenfalls 3 stellig. Erfahrene C Programmierer mögen
Schönheitsfehler entschuldigen.
Ratschläge und Hinweise:
Ein Vorsatz kann durchaus nützlich sein: Kurzbezeichnung, Erstellungsdatum usw.
Dass ich dem Vorsatz kein Längenfeld mitgegeben habe ist ein Gag.
Geben sie allen Sätzen in der IT eine Satzart, zB die ersten 4 Stellen.
Zu jeder Satzart gehört ein bestimmter Satzaufbau der als struct in einem include
vorliegen kann.
Sie können dann verschiedene Dateien zusammenführen.
Die wiederverwendbarkeit von Code lässt sich durch Verwendung von functions
und includes erreichen.
Verändern sie NIE die Felder der eingelesenen Sätze!
In der obiektorientierten Programmierung könte man das erzwingen, aber ich
glaube es ist nicht schwer sich an diese Regel zu halten.
Erlauben sie mir einige Tränen für das gepackte Format zu vergießen: Jedes
Halbbyte enthält eine Ziffer von 0 - 9 (binär). Das Vorzeichen wird in einem
Halbbyte mit (hex) C für + und D für - dargestellt. In 6 Bytes bringt man also 11
Stellen mit Vorzeichen unter. Der Vorteil: Man kann die gepackte Zahl IM DUMP
LESEN! Versteht man das noch?
Downloads