Floppy-Kurs: "Es rappelt in der Kiste" (Teil 2)
Hallo zum zweiten Teil des Floppy-Kurses. In der vorletzten MD hatten einiges über den Floppybefehlskanal und
die Programmierung sequentieller Files
gelernt. In diesem Teil wollen wir uns
nunmehr mit der relativen Dateiverwaltung beschäftigen, die zwar etwas komplizierter 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 hinter 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 geregelt. Sie arbeiten mit sogenannten " Datensätzen", oder engl." Records" . Im
Prinzip kann man eine relative Datei mit
einem externen Variablenfeld vergleichen. 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. Dennoch 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 schreiben 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 " Schreiben" unterschieden. Wir öffnen ein solches File ganz einfach um der Floppy
anzuzeigen, daß wir es nun benutzen wollen. Ob wir nun lesen oder schreiben ist
ganz egal. Die Floppy erkennt automatisch, wenn wir etwas schreiben oder
lesen. Das heißt also, daß wir bei der
Benutzung eines relativen Files immer
auch den Floppybefehlskanal öffnen müssen. Ö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önnen. Bevor wir das tun, sollten wir uns
überlegen, wie lang ein Datensatz unseres 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 Files immer gleich lang sein müssen, um
der Floppy das Auffinden eines Datensatzes 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 diesen angehängt folgt die Typenkennung
", L," sowie der Länge des Datensatzes
in diesem File. Hier ein kleiner Hinweis: 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 Nummern 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 eigentlich ganz einfach: wir sprechen lediglich 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 Beispiel also alle 300 . Das Generieren dieser Sätze kann jetzt einige Zeit in Anspruch 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 relativen Files.
Wenn die Floppy mit dem Anlegen der relativen Datei fertig ist blinkt sie
übrigens sowieso, da wir durch den Zugriff 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 Lowund 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 Datensä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 Positionierungsbefehl an die Floppy. Wohlgemerkt
geschieht dies über den Befehlskanal!
Aus dem Beispiel ist die Syntax des Positionierungsbefehls ersichtlich. Beginnend mit dem Zeichen " P"( für " positionieren") werden in Reihenfolge die Sekundäradresse der REL-Datei auf die sich
die Positionierung beziehen soll, die
Datensatznummer in Lo/ Hi-Folge, sowie
die Byteposition innerhalb des entsprechenden 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 positionierten 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 erstellten REL-Datei arbeiten, so müssen
wir sie natürlich öffnen. Hierbei ist
darauf zu achten, daß wir dieselbe Datensatzlänge angeben, wie wir sie beim
Erzeugen der Datei verwendet haben. Andernfalls kommt die Floppy nämlich mit
der Verwaltung der Datensätze durcheinander, was verheerende Folgen bei
Schreibzugriffen haben kann. Benutzen
Sie am Besten also den gleichen OPEN-Befehl, den Sie auch beim Erstellen benutzt 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 Datensatz 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 geschrieben. Da der Datensatz diesmal schon existiert 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 verwenden. Dieser erkennt das Ende eines Lesevorgangs 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üssen 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 Datensatz 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 Endmarkierung ( das " CR") für den INPUT#- Befehl
geschrieben wurde.
DER LESEZUGIFF
Auch der Lesezugriff weicht nicht sonderlich von den bisherigen Beispielen
ab. Wie immer öffnen die beiden Floppykanäle und positionieren auf den
gewünschten Datensatz. Nun haben wir
zwei Möglichkeiten unsere Daten wieder auszulesen:
Wurden die Daten OHNE Semikolon geschrieben, 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 Anmerkungen:
1) Bei der GET#- Abfrage sollten nicht
mehr Zeichen gelesen werden, als maximal in dem Datensatz vorhanden sein
können. Bei einer Länge von 80 Zeichen wird die GET#- Schleife also
nicht mehr als 80 mal durchlaufen.
2) Was tun wir, wenn der Datensatzinhalt
kürzer ist, als die festgelegte Datensatzlänge? Wie ich oben schon einmal 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. Schreiben 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 Vorraussagen eigentlich relativ einfach ist.
Wir müssen jeweils nur richtig positionieren 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 ansprechen.
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üssen. 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änge eines Datenfeldes beschränken. Sie
könnten nun für jedes Datenfeld eine
eigene REL-Datei anlegen, also beispielsweise 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 jedesmal, wenn Sie eine komplette Adresse
ausgeben wollen, alle Ihre REL-Files
nacheinander öffnen, lesen und schließen
müssen. Was das an Programmierund
Zeitaufwand beim Zugriff bedeutet ist
verheerend. Deshalb geht man in der Regel 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 sollen. Für unsere kleine Adressverwaltung
wollen wir 6 Felder pro Datensatz definieren:
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 Datensatz jeweils alle 6 Felder abgelegt werden. Dadurch ergibt sich eine Datensatzlänge von 114 Zeichen (20+15+30+4+30+15=114) . Wir können nun wiederum zwei
Wege gehen, mit denen wir die sechs Felder 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 unflexiblere, Methode sieht folgendermaßen aus:
Wir schreiben mit mehreren PRINT#- Befehlen OHNE Semikolon alle Stringvariablen 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 schreiben, wie dieser lang ist, was man tunlichst unterlassen sollte) . Je nach dem, wie flexibel unser Adressverwaltungsprogramm sein soll entstehen nun jedoch
diverse Schwierigkeiten. So müssen wir
zum Beispiel immer alle Felder eines
Datensatzes einlesen, wenn wir eine Datei z. B. nach einem einzelnen Feld sortieren möchten. Für gerade diese Aufgabe werden dann immer 5 Felder zuviel gelesen, was sich wiederum auf die Verarbeitungszeit ReLativ auswirkt. Das zweite
Problem ist die Endmarkierung nach jedem
Feldeintrag. Wie oben ja schon dargestellt 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 sicherlich unflexibler ist.
( bitte Teil 2 Laden. . . .)