Floppy-Kurs: "Es rappelt in der Kiste"
(Teil 2)
---------------------------------------
Hallo zum zweiten Teil des Floppy-
Kurses. In der vorletzten MD hatten ei-
niges über den Floppybefehlskanal und
die Programmierung sequentieller Files
gelernt. In diesem Teil wollen wir uns
nunmehr mit der relativen Dateiverwal-
tung beschäftigen, die zwar etwas kom-
plizierter zu programmieren, dafür aber
weitaus flexibler als die sequentielle
Dateiverwaltung zu handhaben ist.
DIE RELATIVE DATENVERWALTUNG
Im ersten Teil des Floppy-Kurses hatten
wir bei den von der Floppy unterstützten
Filetypen auch die sogenannten REL-Files
besprochen. Sie bezeichnen Dateien, die
einen RELativen Aufbau haben. Was das
bedeutet wollen wir nun klären.
Sicherlich erinnern Sie sich, daß die
sequentiellen Files, wie der Name schon
sagt Daten "sequentiell", also Byte hin-
ter Byte enthalten. Wenn wir Daten in
einem solchen File gespeichert hatten,
so mussten wir immer das komplette File
in den Rechner einlesen, um die Daten
weiterverwnden zu können. Bei relativen
Files ist dieser Aufbau nun anders gere-
gelt. Sie arbeiten mit sogenannten "Da-
tensätzen", oder engl. "Records". Im
Prinzip kann man eine relative Datei mit
einem externen Variablenfeld verglei-
chen. Wir geben der Floppy lediglich an,
daß wir z.B. das 24. Element (sprich:
Record) ansprechen wollen, und schon
können wir es lesen oder schreiben. Bei
einem sequentiellen File hätten wir die
23 Eintragungen vorher erst überlesen
müssen, bis wir auf das gewollte Element
hätten zugreifen können.
Die Vorteile von REL-Files liegen damit
auf der Hand:
1) Schnellerer Zugriff, da wir nur die
benötigten Daten ansprechen müssen.
2) Speicherersparnis im Rechner selbst,
da alle Daten extern gelagert sind
und kein Speicherplatz mit momentan
nicht benötigten Daten belegt wird.
Jedoch hat die relative Dateiverwaltung
auch einen Nachteil: da sie von BASIC
aus nicht unterstützt wird, ist ihre
Programmierung relativ umständlich. Den-
noch lässt sich auch das lernen, und wer
es einmal kann der wird merken, daß die
Vorteile überwiegen.
DIE PROGRAMMIERUNG
Bevor wir überhaupt irgendetwas aus oder
in ein relatives File lesen oder schrei-
ben können müssen wir es erst einmal
generieren. Öberhaupt wird ein relatives
File ganz und gar anders behandelt als
ein sequentielles. So wird beim Üffnen
nicht mehr zwischen "Lesen" und "Schrei-
ben" unterschieden. Wir öffnen ein sol-
ches File ganz einfach um der Floppy
anzuzeigen, daß wir es nun benutzen wol-
len. Ob wir nun lesen oder schreiben ist
ganz egal. Die Floppy erkennt automa-
tisch, wenn wir etwas schreiben oder
lesen. Das heißt also, daß wir bei der
Benutzung eines relativen Files immer
auch den Floppybefehlskanal öffnen müs-
sen. Öber spezielle Befehle wird die
Floppy dann über die folgende Operation
informiert. Sie stellt dann, je nach
Befehl, beim Lesen auf dem relativen
Filekanal die gewünschten Daten bereit,
bzw. erwartet auf diesem die Daten, die
geschrieben werden sollen.
Wollen wir nun also einmal ein relatives
File anlegen, damit wir es benutzen kön-
nen. Bevor wir das tun, sollten wir uns
überlegen, wie lang ein Datensatz unse-
res REL-Files werden soll, und wie viele
davon wir vorläufig darin speichern wol-
len. Ersteres müssen wir deshalb tun,
weil die Datensätze eines relatives Fi-
les immer gleich lang sein müssen, um
der Floppy das Auffinden eines Datensat-
zes zu ermöglichen. Nehmen wir einfach
einmal an, daß wir 300 Datensätze zu je
80 Zeichen anlegen wollen. Wie oben
schon erwähnt, öffnen wir zuerst einmal
den Floppybefehlskanal. Anschließend
folgt der OPEN-Befehl für das relative
File. Wir legen dabei wie gewohnt die
logische Filenummer, die Gerätenummer
und die Sekundäradresse fest, und geben
einen Namen für unsere Datei an. An die-
sen angehängt folgt die Typenkennung
",L," sowie der Länge des Datensatzes
in diesem File. Hier ein kleiner Hin-
weis: im ersten Teil dieses Kurses
erwähnte ich, daß die Kennung für eine
relative Datei beim Öffnen ein "R" ist.
Das war leider eine Fehlinformation! "L"
ist die richtige Bezeichnung für den
OPEN-Befehl!
Hier nun ein Beispiel, in dem wir das
relative File "TEST" mit 80 Zeichen pro
Datensatz eröffnen:
OPEN 1,8,15
OPEN 2,8,3,"TEST,R,"+CHR$(80)
Der Befehlskanal hat nun die logische
Filenummer "1", das relative File die
"2". Wichtig beim Üffnen desletzeren ist
auch die Wahl der Sekundäradresse, da
diese bei der Befehlsübergabe an den
Befehlskanal verwendet wird. Wählen Sie
bei der Sekundäradresse bitte nicht die
vorreservierten Nummern 0,1 und 15 (sh.
Floppy-Kurs, Teil 1), sondern nur Num-
mern zwischen 2 und 14.Nachdem wir nun
also alles geöffnet hätten, was wir
benötigen, müssen wir jetzt erst einmal
die gewünschten 300 Datensätze in der
REL-Datei anlegen. Das funktioniert ei-
gentlich ganz einfach: wir sprechen le-
diglich mit einem speziellen Befehl den
300. Datensatz an, und schreiben den
Wert 255 hinein. Die Floppy generiert in
diesem Fall nämlich automatisch alle
fehlenden Datensätze - in unserem Bei-
spiel also alle 300. Das Generieren die-
ser Sätze kann jetzt einige Zeit in An-
spruch nehmen, da die Floppy nun den
Speicherplatz, den sie für alle Sätze
braucht, automatisch belegt und mit den
Bytewerten 255 füllt. Stören Sie sich
bitte nicht daran, wenn während dieses
Arbeitsgangs (wie auch bei allen anderen
Operationen mit relativen Files) die
Floppy-LED zu blinken beginnt, oder
trotz Zugriffs zeitweise erlischt. Das
ist ganz normal bei der Arbeit mit rela-
tiven Files.
Wenn die Floppy mit dem Anlegen der re-
lativen Datei fertig ist blinkt sie
übrigens sowieso, da wir durch den Zu-
griff auf einen noch nicht existierenden
Datensatz einen "Record not present"-
Fehler erzeugt haben, der uns jedoch
nicht weiter stören soll. Durch Auslesen
des Befehlskanals stoppen wir das LED-
Blinken.Hier nun das Ganze als Programm.
10 OPEN 1,8,15
20 OPEN 2,8,3,"TEST,R,"+CHR$(80)
30 HI=INT(300/256): LO=300-256*HI
40 PRINT#1,"P"+CHR$(3)+CHR$(LO)+CHR$(HI)
+CHR$(1)
50 PRINT#2,CHR$(255)
60 INPUT#1,A,B$,C,D:
70 PRINT A,B$,C,D
80 CLOSE1: CLOSE2
Die OPEN-Befehle aus den Zeilen 10 und
20 kennen wir ja schon. In Zeile 30
spalten wir die Zahl 300 (die Anzahl der
Datensätze, die wir in unserer Datei
verwenden möchten) in Low- und High-Byte
auf, da mit dem CHR$-Befehl ja immer nur
8-Bit-Werte (von 0 bis 255) übergeben
werden können, und durchaus mehr Da-
tensätze möglich sein können, müssen wir
einen 16-Bit-Wert in Lo/Hi-Folge an die
Floppy senden. Dies geschieht in der
folgenden Zeile - mit dem ersten
PRINT#-Befehl senden wir den Positionie-
rungsbefehl an die Floppy. Wohlgemerkt
geschieht dies über den Befehlskanal!
Aus dem Beispiel ist die Syntax des Po-
sitionierungsbefehls ersichtlich. Begin-
nend mit dem Zeichen "P" (für "positio-
nieren") werden in Reihenfolge die Se-
kundäradresse der REL-Datei auf die sich
die Positionierung beziehen soll, die
Datensatznummer in Lo/Hi-Folge, sowie
die Byteposition innerhalb des entspre-
chenden Datensatzes mittels CHR$-Codes
an die Floppy übermittelt. In Zeile 50
wird nun über den logischen Kanal des
REL-Files der Wert 255 in den positio-
nierten Datensatz geschrieben. Da er,
wie alle anderen vor ihm, noch nicht
existiert, beginnt die Floppy nun damit
alle Datensätze anzulegen, um den
Schreibbefehl in Record 300 ausführen zu
können. Es ist übrigens wichtig, daß Sie
beim Erzeugen einer REL-Datei den Wert
255 schreiben, weil dieser nämlich als
Endmarkierung beim Lesen dient. Hierzu
jedoch später mehr.
In den Zeile 60 und 70 lesen wir nun
noch den Fehlerkanal aus und geben die
"Record not present"-Fehlermeldung aus,
um die blinkende Floppy-LED zu löschen
und schließen anschließend die beiden
offenen Files - schon haben wir eine
REL-Datei zur Verfügung!
DER SCHREIBZUGRIFF
Möchten wir nun mit unserer selbst er-
stellten REL-Datei arbeiten, so müssen
wir sie natürlich öffnen. Hierbei ist
darauf zu achten, daß wir dieselbe Da-
tensatzlänge angeben, wie wir sie beim
Erzeugen der Datei verwendet haben. An-
dernfalls kommt die Floppy nämlich mit
der Verwaltung der Datensätze durchein-
ander, was verheerende Folgen bei
Schreibzugriffen haben kann. Benutzen
Sie am Besten also den gleichen OPEN-
Befehl, den Sie auch beim Erstellen be-
nutzt haben!!!
Wenn wir jetzt etwas in unsere Datei
schreiben möchten, so verfahren wir im
Prinzip genauso, wie beim Erstellen der
Datei (denn das war ja nichts anderes
als das Schreiben in eine REL-Datei).
Wir öffnen also zunächst Befehlskanal
und REL-Datei, positionieren mittels
Befehlskanal auf den gewünschten Daten-
satz und schreiben über die logische
Filenummer der REL-Datei Daten in diesen
Satz hinein. Hier ein Beispiel:
10 OPEN 1,8,15
20 OPEN 2,8,3,"TEST,R,"+CHR$(80)
30 PRINT#1,"P"+CHR$(3)+CHR$(1)+CHR$(0)+
CHR$(1)
40 PRINT#2,"DIESER TEXT WIRD NUN IN DA
TENSATZ NUMMER EINS GESPEICHERT!";
50 CLOSE1: CLOSE2
Im Positionierbefehl wird wieder die
Sekundäradresse 3 verwendet. Diesmal
positionieren wir jedoch auf Byte 1 des
ersten Datensatzes unserer Datei "TEST"
(Die Werte 1 und 0 entsprechen der
LO/HI-Darstellung der Zahl 1 --> 256*0+1
= 1). In Zeile 40 wird dann mit einem
ganz normalen PRINT#-Befehl ein Text in
den positionierten Datensatz geschrie-
ben. Da der Datensatz diesmal schon exi-
stiert brauchen wir demnach auch keinen
Fehler von der Floppy auszulesen, da
vorraussichtlich keiner auftritt.
Anstelle des Textes könnte natürlich
auch eine Stringvariable stehen. Achten
Sie bitte darauf, daß Sie nie längere
Texte in einen Datensatz schreiben, als
selbiger lang ist, da Sie sonst einen
Teil der Daten im folgenden Datensatz
verlieren könnten.
Wichtig ist auch, ob Sie beim Schreiben
das Semikolon (";") verwenden, oder
nicht. Verwenden Sie es nicht, so können
Sie beim Lesen den INPUT#-Befehl verwen-
den. Dieser erkennt das Ende eines Lese-
vorgangs immer an einem "Carriage Return
Code" (kurz "CR" = CHR$(13)), der von
einem PRINT# OHNE Semikolon immer auto-
matisch gesendet wird. In dem Fall müs-
sen Sie aber auch bedenken, daß ein Text
den Sie schreiben nie länger als die
Datensatzlänge-1 sein darf, da das CR
ebenfalls als ganzes Zeichen in den Da-
tensatz geschrieben wird.
Nun ist, wie wir später sehen werden,
das Lesen mittels INPUT# nicht immer von
Vorteil, weshalb Sie einen Text auch MIT
Semikolon schreiben können. In dem Fall
müssen wir später beim Lesen eine GET#-
Schleife verwenden, da keine Endmarkie-
rung (das "CR") für den INPUT#-Befehl
geschrieben wurde.
DER LESEZUGIFF
Auch der Lesezugriff weicht nicht son-
derlich von den bisherigen Beispielen
ab. Wie immer öffnen die beiden Floppy-
kanäle und positionieren auf den
gewünschten Datensatz. Nun haben wir
zwei Möglichkeiten unsere Daten wieder
auszulesen:
Wurden die Daten OHNE Semikolon ge-
schrieben, so genügt ein einfaches
"INPUT#2,A$" um unseren Text wieder zu
Lesen und in A$ abzulegen.
Wurden Sie MIT Semikolon geschrieben, so
müssen wir den umständlicheren Weg über
eine GET#-Abfrage gehen. Dazu zwei An-
merkungen:
1) Bei der GET#-Abfrage sollten nicht
mehr Zeichen gelesen werden, als ma-
ximal in dem Datensatz vorhanden sein
können. Bei einer Länge von 80 Zei-
chen wird die GET#-Schleife also
nicht mehr als 80 mal durchlaufen.
2) Was tun wir, wenn der Datensatzinhalt
kürzer ist, als die festgelegte Da-
tensatzlänge? Wie ich oben schon ein-
mal erwähnte, dient der Byte-Wert 255
als Endmarkierung innerhalb einer
REL-Datei. Dies stellt sich so dar,
daß ein leerer Datensatz alle Bytes
mit dem Wert 255 gefüllt hat. Schrei-
ben wir nun einen Text in diesen Da-
tensatz, so werden alle benötigten
Zeichen mit dem Text überschieben.
Das darauf folgende Zeichen enthält
dann aber immer noch den Wert 255.
Dementsprechend können wir sagen, daß
wenn wir beim Lesen eines Strings
dieses Zeichen erhalten, der String
zu Ende sein muß.
Durch diese beiden Punkte ergibt sich
also folgende Schleife zum Lesen eines
Strings:
90 ...
100 A$=""
110 FOR i=1 TO 80
120 GET#2,B$
130 IF ASC(B$)=255 THEN 160
140 A$=A$+B$
150 NEXT
160 ...
DATABANKING MIT RELATIVEN FILES
Sie sehen, daß das Arbeiten mit REL-
Files trotz aller Gegenteiligen Vorraus-
sagen eigentlich relativ einfach ist.
Wir müssen jeweils nur richtig positio-
nieren und können dann beliebig Lesen
und Schreiben. Nun gibt es jedoch noch
einige Kniffe, die man kennen sollte,
wenn man effektiv mit relativen Dateien
arbeiten möchte. Diese will ich nun an-
sprechen.
DATENFELDER:
Bei jeder Datenverarbeitung werden Sie
meist mehrere Angaben in einem Datensatz
machen. Einfachstes Beispiel ist hier
eine Adressverwaltung. Hier müssen Sie
pro Datensatz einen Namen, Strasse,
Wohnort, Telefonnummer, etc. angeben,
die Sie nachher auch immer wieder auf
anhieb in Ihrer Adressdatei finden müs-
sen. Diese einzelnen Einträge in einem
Datensatz nennt man Datenfelder. Wie bei
relativen Files so üblich, sollte man
sich dann jeweils auf eine maximale Län-
ge eines Datenfeldes beschränken. Sie
könnten nun für jedes Datenfeld eine
eigene REL-Datei anlegen, also bei-
spielsweise eine Datei mit 30 Zeichen
pro Satz für alle Namen, eine mit 40
Zeichen für alle Straßen, eine mit 15
Zeichen für alle Telefonnummern, eine
mit 4 Zeichen für alle Postleitzahlen
usw. Diese Lösung birgt jedoch ein grö-
ßeres Problem in sich: der C64 verwaltet
nämlich immer nur EINE offene REL-Datei.
Das bedeutet in der Praxis, daß Sie je-
desmal, wenn Sie eine komplette Adresse
ausgeben wollen, alle Ihre REL-Files
nacheinander öffnen, lesen und schließen
müssen. Was das an Programmier- und
Zeitaufwand beim Zugriff bedeutet ist
verheerend. Deshalb geht man in der Re-
gel einen anderen Weg. Halen wir doch
einfach einmal an dem Beispiel der
Adressverwaltung fest. Zunächst wollen
wir uns einmal überlegen, welche und
wieviele Felder wir verwenden wollen,
und wie lang sie im einzelnen sein sol-
len. Für unsere kleine Adressverwaltung
wollen wir 6 Felder pro Datensatz defi-
nieren:
1) "Name" (20 Zeichen)
2) "Vorname" (15 Zeichen)
3) "Straße" (30 Zeichen)
4) "Postleitzahl" ( 4 Zeichen)
5) "Ort" (30 Zeichen)
6) "Telefon" (15 Zeichen)
Kommen wir nun zu dem Lösungsweg, denn
man hier in der Regel geht. Anstelle von
6 einzelnen Dateien legen wir nun eine
einzige Datei an, in der in einem Daten-
satz jeweils alle 6 Felder abgelegt wer-
den. Dadurch ergibt sich eine Daten-
satzlänge von 114 Zeichen (20+15+30+4+
30+15=114). Wir können nun wiederum zwei
Wege gehen, mit denen wir die sechs Fel-
der in einem Datensatz speichern. Ich
gehe dabei davon aus, daß die Einträge
vom Programm schon abgefragt wurden und
in Stringvariablen stehen:
Die einfachere, dafür jedoch unflexible-
re, Methode sieht folgendermaßen aus:
Wir schreiben mit mehreren PRINT#-
Befehlen OHNE Semikolon alle Stringva-
riablen hintereinander in ein Datenfeld
hinein. Später können wir sie genauso
wieder mittels INPUT# einlesen. Hierbei
ist es egal, wie lang ein Feldeintrag
ist, solange er die vorgegebene Länge
nicht überschreitet (wäre das bei allen
Feldern nämlich der Fall, so würden wir
mehr Zeichen in einen Datensatz schrei-
ben, wie dieser lang ist, was man tun-
lichst unterlassen sollte). Je nach dem,
wie flexibel unser Adressverwaltungspro-
gramm sein soll entstehen nun jedoch
diverse Schwierigkeiten. So müssen wir
zum Beispiel immer alle Felder eines
Datensatzes einlesen, wenn wir eine Da-
tei z.B. nach einem einzelnen Feld sor-
tieren möchten. Für gerade diese Aufgabe
werden dann immer 5 Felder zuviel gele-
sen, was sich wiederum auf die Verarbei-
tungszeit ReLativ auswirkt. Das zweite
Problem ist die Endmarkierung nach jedem
Feldeintrag. Wie oben ja schon darge-
stellt müssen wir bei dieser Methode
OHNE Semikolon arbeiten, und in dem Fall
hängt PRINT# immer ein 'CR' an einen
String an. Dadurch müssen wir von den
obigen Feldlängen jeweils ein Zeichen
abziehen (der Name z.B. darf nicht mehr
20, sondern nur noch 19 Zeichen lang
sein).
Sie sehen also, daß diese Methode zwar
einfacher zu programmieren, aber sicher-
lich unflexibler ist.
(bitte Teil 2 Laden....)