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....)