Wer sich mit der Programmierung von Dateien beschäftigt, sollte die verschiedenen Lade-und Speicherzeiten berücksichtigen, die in den Tabellen in Bild 1 und2 angegeben sind. Nutzen wir bei der Dateiverwaltung erstens die Schnelligkeit unseres ST und zweitens die des GFA-BASIC.
Wie aus der Tabelle in Bild 2 ersichtlich, werden zum Laden einer sequenziellen Datei mit Input# 104,4 Sekunden benötigt, während dieselben Datensätze mit dem durch >Block-R< gekennzeichneten Verfahren schon nach 5.71 Sekunden zur Verfügung stehen.
Es lohnt sich also, sich bereits bei der Planung eines Programmes hierüber einige Gedanken zu machen.
Die Tabellen in den Bildern 1 und 2 sind Hardcopies von dem in Listing 1 gezeigten Testprogramm (siehe nächste Seite). Bei Benutzung einer RAM-Disk ergeben sich weitere Vorteile, die bei einer Hard-Disk ähnlich liegen dürfte (ich konnte dies leider nicht testen).
Nun zu einzelnen Varianten: Gespeichert wurden 375 Datensätze zu je 75 Zeichen. Die unterschiedliche Länge der Dateien resultiert aus dem beim PRINT#-Befehl angehängten Carriage Return (13) und Line Feedt (10), das sind je Datensatz 12 Bytes. PRINT# und INPUT# sind sehr zeitaufwendig und sollten vor allem bei größeren Dateien nicht benutzt werden. Schneller geht es mit den Random-Access-Dateien, deren einzelne Felder und damit die Datensätze gleiche Längen haben - in unserem Beispiel 6 Felder mit 15, 10,20, 8,12 und 10 Zeichen (gesamt 75). Ein weiterer Vorteil dieser Dateiart mit PUT# und GET# ist die Zugriffsmöglichkeit auf einzelne Datensätze in der Datei.
Es geht aber Dank der leistungsfähigen GFA-Befehle noch schneller. Die gesamte Datei wird als Block von Diskette in einen String geladen bzw. aus einem String als Block gespeichert. Vor dem Laden muß natürlich ein String mit der erforderlichen Länge zur Aufnahme bereitstehen. Von diesem String (Dat$ im Listing) aus werden dann die einzelnen Datensätze DS(374,5) aufbereitet. Beim Speichern werden die einzelnen Datensätze vor dem Speichern in den String aufgenommen. Das hört sich alles ein bißchen umständlich an, aber es ist die schnellste Methode, Die Datensätze können nach Herzenslust verändert, gelöscht und angefügt werden und sind dann schnell wieder abgespeichert. Man hüte sich jedoch davor, den String nach dem Motto 'Dat$=Dat$+D$(x,y)’ aufzubauen. Beim Ausprobieren bitte Kaffeepause einlegen. Im Testprogramm werden hierfür BMOVE und Mid$ verwendet, in den Tabellen 1 und 2 ist diese Variante mit Bsave und Block-R gekennzeichnet.
Bleibt noch das Verfahren Block-I zu erklären. Es ist anzuwenden zum Laden von Dateien, die mit PRINT# abgespeichert sind. Die Datei wird zunächst ebenfalls als Block in einen vorbereiteten String geladen, dann aber wegen der unterschiedlichen Datenlängen Byte für Byte nach den abschließenden Carriage Returns abgesucht und so die einzelnen Datenfelder herausgefischt. Gegenüber dem INPUT#-Befehl ergibt sich eine Zeitersparnis von ca. 60%, es können alle sequenziellen Dateien damit eingelesen werden und evtl, nach vorheriger Aufbereitung auf einheitliche Längen mit Bsave wieder abgespeichert werden.
Nachdem nun die Vorzüge einer solchen Block-Datei aufgezeigt wurden, soll auch der Nachteil nicht verschwiegen werden. Die in den Tabellen dargestellte Dateilänge hat fast die maximale Größe von 32767 Bytes erreicht. Größer kann ein String nicht definiert werden (oder vielleicht doch mit dem neuen GFA V 3.0?).
Aber lassen wir den Kopf nicht hängen, unser GFA-BASIC hält ja noch weitere sehr gute Befehle bereit.
Wir RESERVEieren uns einfach den benötigten Speicherbereich und verwenden statt Varptr(Dat$) = H1MEM.
Ein Test mit 2000 Datensätzen (150000 Bytes) ergab:
Speichern mit PUT# 179.17 Sekunden
Bsave (Himem) 48.25 Sekunden
Laden mit GET# 65.48 Sekunden
Bload (Himem) 35.06 Sekunden
Beim Reservieren von Speicher sollte man darauf achten, daß nicht mehr als notwendig reserviert wird, andererseits die Datei aber auch in den reservierten Bereich hineinpaßt. Um beim Testen nicht bei jedem Start neuen Speicherplatz zu reservieren, kann der RESERVE-Befehl in eine If-Abfrage verpackt werden z.B. beim ST 1040:
If Himem >700000
Reserve Fre(0)-300000
Endif
Nun noch einige Erklärungen zum Listing: Das Testprogramm besteht im Wesentlichen aus der Initialisierungsroutine mit dem Aufbau der Menüleiste und der Tabellengrafik sowie der Dimensionierung der Variablen, der Hauptschleife und den einzelnen Prozeduren.
Die benutzerfreundliche und einfach zu programmierende Menüleiste dürfte auch für den Einsteiger keine Probleme aufwerfen. Nach der Initialisierung wartet der ST in der nur 3 Zeilen großen Warteschleife auf einen Menüaufruf, landet dann in der Prozedur Auswahl, sucht sich hier den angeklickten Menüpunkt, arbeitet dann das entsprechende Unterprogramm ab und begibt sich, nachdem die Menüleiste aufgebaut ist, wieder zur Warteschleife.
Die einzelnen Prozeduren für das Laden und Speichern der Dateien bedürfen keiner besonderen Erklärung, die besonderen Funktionen wurden bereits erläutert. In Prozedur Eil werden Dateiname (Fi$) und Länge der Datei (L%, falls schon vorhanden) ermittelt. Die Prozedur Zeit errechnet den benötigten Zeitaufwand einschließlich Datenaufbereitung und gibt diese in einer Tabelle auf den Bildschirm aus. Mit der Prozedur Ausgabe werden die Datensätze auf dem Bildschirm ausgegeben, damit auch die letzten Zweifel auf das sichere Funktionieren beseitigt werden. Die Datensätze können mit der Maus vorwärts und rückwärts gescrollt werden. Durch Drücken beider Maustasten gelangt man zurück zur Warteschleife. Die umständliche Ausführung der Print-Befehle rührt von der ungünstigen Länge der Datensätze her. Die bereits festgelegten Feldlängen wollte ich nicht mehr ändern und die einzelnen Felder trotzdem mit einem Leerzeichen listen. Schließlich werden in der Prozedur Anlegen die Test-Datensätze erzeugt.
Es ist noch anzumerken, daß mit Rücksicht auf die Länge dieses Testprogrammes keine vollständige Fehlerbehandlung integriert ist. Diese darf in einem Anwendungsprogramm jedoch keineswegs fehlen. So muß z.B. das Programm abgefangen werden, wenn versucht wird, eine Datei abzuspeichem. ohne daß sich eine solche im Speicher befindet oder die Fehlermeldung nach Gosub Fil bei Anklicken von Abbruch und ok mit Leerstring (nur Len<FiS)>2 erlauben).
Diese Fehler können bei den einzelnen Prozeduren lokalisiert und bequem in eine gemeinsame Stringvariable verpackt werden, die dann am Ende der Prozedur Auswahl auf den Bildschirm ausgegeben werden kann und so auf unterlassene Bedienungshandlungen aufmerksam macht.
' #################################
' # Schnelle Dateien in GfA-Basic #
' # Schott Franz #
' # Bahnhofsplatz 3 #
' # 8425 Neustadt/Donau #
' # (c) MAXON Computer GmbH #
' #################################
GOSUB i.nit
DO
ON MENU
LOOP
PROCEDURE auswahl
MENU OFF
m0%=MENU(0)
CLS
IF wahl$(m0%)=" erstellen "
GOSUB anlegen
ENDIF
IF wahl$(m0%)=" Datei "
GOSUB ausgabe
ENDIF
IF wahl$(m0%)=" Zeittabelle "
SPUT bild$
REPEAT
UNTIL MOUSEK OR INKEY$<>""
ENDIF
IF wahl$(m0%)=" Print# "
GOSUB s.ave_print
ENDIF
IF wahl$(m0%)=" Bsave "
GOSUB s.ave_bsave
ENDIF
IF wahl$(m0%)=" Put# "
GOSUB s.ave_put
ENDIF
IF wahl$(m0%)=" Get# "
GOSUB l.oad_get
ENDIF
IF wahl$(m0%)=" Input# "
GOSUB l.oad_input
ENDIF
IF wahl$(m0%)=" Block-R "
GOSUB l.oad_block_r
ENDIF
IF wahl$(m0%)=" Block-I "
GOSUB l.oad_block_i
ENDIF
IF wahl$(m0%)=" Ende "
EDIT
ENDIF
CLS
MENU wahl$()
RETURN
PROCEDURE i.nit
CLS
re$=CHR$(27)+"p"
ra$=CHR$(27)+"q"
PRINT AT(7,5);re$'
PRINT "Lade- und Speicherzeiten von Dateien mit GfA-Basic in Sekunden ";ra$
PRINT
PRINT '"Länge S P E I C H E R N L a d e n "
PRINT '"Byte Print# Put# Bsave Input# Get# Block-R ";
PRINT "Block-I Datei"
LINE 0,50,639,50
LINE 0,134,639,134
LINE 0,94,639,94
LINE 64,94,64,300
FOR n=132 TO 630 STEP 72
LINE n,112,n,300
NEXT n
LINE 276,94,276,300
LINE 564,94,564,300
SGET bild$
CLS
DIM d$(2500,5)
DIM wahl$(35)
DIM f$(5)
RESTORE m.datas
i=0
DO
READ wahl$(i)
EXIT IF wahl$(i)="~~~"
INC i
LOOP
wahl$(i)=""
m.datas:
DATA DESKTOP, Information,--------------------
DATA , , , , , ,""
DATA DATENSÄTZE, erstellen ,""
DATA LADEN, Input# , Get# , Block-R , Block-I ,""
DATA SPEICHERN, Print# , Put# , Bsave ,""
DATA AUSGABE, Datei , Zeittabelle ,""
DATA QUIT, Ende ,""
DATA ~~~,""
ON MENU GOSUB auswahl
ON BREAK GOSUB ende
MENU wahl$()
RETURN
PROCEDURE l.oad_get
GOSUB fil
t=TIMER
OPEN "R",#1,fi$,75
FIELD #1,15 AS d0$,10 AS d1$,20 AS d2$,8 AS d3$,12 AS d4$,10 AS d5$
FOR n%=1 TO l%/75
GET #1,n%
d$(n%-1,0)=d0$
d$(n%-1,1)=d1$
d$(n%-1,2)=d2$
d$(n%-1,3)=d3$
d$(n%-1,4)=d4$
d$(n%-1,5)=d5$
NEXT n%
z%=n%-1
CLOSE #1
t1=TIMER
sp%=47
GOSUB zeit
RETURN
PROCEDURE l.oad_block_r
GOSUB fil
IF l%<=32767
t=TIMER
dat$=SPACE$(l%)
BLOAD fi$,VARPTR(dat$)
z%=0
pa%=1
feld:
DATA 15,10,20,8,12,10
REPEAT
RESTORE feld
FOR r%=0 TO 5
READ f%
d$(z%,r%)=MID$(dat$,pa%,f%)
ADD pa%,f%
NEXT r%
INC z%
UNTIL pa%=>l%
t1=TIMER
sp%=56
GOSUB zeit
ELSE
ALERT 3,"Datei ist zu groß.|Bitte Himem verwenden!",1,"klar",a%
ENDIF
RETURN
PROCEDURE l.oad_block_i
GOSUB fil
IF l%<32767
t=TIMER
dat$=SPACE$(l%)
BLOAD fi$,VARPTR(dat$)
z%=0
pa%=0
DO
FOR r%=0 TO 5
pe%=0
REPEAT
INC pe%
UNTIL MID$(dat$,pa%+pe%,1)=CHR$(13) OR pe%>500
d$(z%,r%)=SPACE$(pe%-1)
BMOVE VARPTR(dat$)+pa%,VARPTR(d$(z%,r%)),pe%-1
ADD pa%,pe%+1
NEXT r%
INC z%
EXIT IF pa%=>l% OR pe%>500
LOOP
t1=TIMER
sp%=65
GOSUB zeit
ENDIF
RETURN
PROCEDURE l.oad_input
l%=0
GOSUB fil
PRINT AT(26,12);"B i t t e G e d u l d"
t=TIMER
IF l%
z%=0
OPEN "I",#1,fi$
REPEAT
FOR r%=0 TO 5
INPUT #1,d$(z%,r%)
EXIT IF EOF(#1)=-1
NEXT r%
INC z%
UNTIL EOF(#1)=-1
CLOSE #1
t1=TIMER
sp%=38
GOSUB zeit
ENDIF
RETURN
PROCEDURE s.ave_put
GOSUB fil
l%=z%*75
t=TIMER
OPEN "R",#1,fi$,75
FIELD #1,15 AS d0$,10 AS d1$,20 AS d2$,8 AS d3$,12 AS d4$,10 AS d5$
FOR n%=0 TO z%-1
d0$=d$(n%,0)
d1$=d$(n%,1)
d2$=d$(n%,2)
d3$=d$(n%,3)
d4$=d$(n%,4)
d5$=d$(n%,5)
PUT #1,n%+1
NEXT n%
CLOSE #1
t1=TIMER
sp%=20
GOSUB zeit
RETURN
PROCEDURE s.ave_print
GOSUB fil
l%=z%*87
IF LEN(fi$)>2
t=TIMER
OPEN "O",#1,fi$
FOR n%=0 TO z%-1
FOR r%=0 TO 5
PRINT #1,d$(n%,r%)
NEXT r%
NEXT n%
CLOSE #1
t1=TIMER
sp%=11
GOSUB zeit
ELSE
ALERT 2,"Keine gültiger Dateiname",1,"0k",a%
ENDIF
RETURN
PROCEDURE s.ave_bsave
IF z%<436
GOSUB fil
l%=z%*75
IF LEN(fi$)>2
t=TIMER
pa%=0
dat$=SPACE$(l%)
FOR n%=0 TO z%-1
RESTORE feld
FOR r%=0 TO 5
READ pe%
BMOVE VARPTR(d$(n%,r%)),VARPTR(dat$)+pa%,pe%
ADD pa%,pe%
NEXT r%
NEXT n%
BSAVE fi$,VARPTR(dat$),l%
t1=TIMER
sp%=29
GOSUB zeit
ELSE
ALERT 2,"Keine gültiger Dateiname",1,"0k",a%
ENDIF
ELSE
a$="Datei ist mit "+STR$(z%*75)+" Bytes|zu groß für den reservierten|Speicher"
ALERT 3,a$,1,"ok",a%
ENDIF
RETURN
PROCEDURE fil
FILESELECT "\*.*","",fi$
IF EXIST(fi$)
OPEN "I",#1,fi$
l%=LOF(#1)
CLOSE #1
ENDIF
RETURN
PROCEDURE zeit
sek=(t1-t)/200
n%=0
ze%=0
REPEAT
INC n%
IF f$(n%)=""
f$(n%)=fi$
ze%=9+n%
ENDIF
IF f$(n%)=fi$
ze%=n%+9
ENDIF
UNTIL ze%>8
SPUT bild$
PRINT AT(sp%,ze%);
PRINT USING "###.##",sek
PRINT AT(1,ze%);
PRINT USING "######",l%
PRINT AT(72,ze%);fi$
PRINT AT(30,24);re$;" T A S T E ";ra$'
SGET bild$
REPEAT
IF INKEY$="1"
BSAVE "zeittest",VARPTR(bild$),16000
ENDIF
UNTIL INKEY$="a" OR MOUSEK
RETURN
PROCEDURE ausgabe
n%=-1
REPEAT
INC n%
FOR r%=0 TO 5
PRINT d$(n%,r%)'
NEXT r%
PRINT CHR$(27);"A"
UNTIL n%=23
DO
MOUSE x,y,k
IF k=1 AND n%<z%-1
INC n%
PRINT AT(1,25);
FOR r%=0 TO 5
PRINT d$(n%,r%)'
NEXT r%
PRINT CHR$(27);"A"
ENDIF
IF k=2 AND n%>23
BMOVE XBIOS(2),XBIOS(2)+1280,30720
DEC n%
PRINT AT(1,1);
FOR r%=0 TO 5
PRINT d$(n%-23,r%);CHR$(0+(32 AND r%<5));
NEXT r%
ENDIF
EXIT IF k=3
LOOP
RETURN
PROCEDURE anlegen
PRINT
PRINT 're$;" Wieviele Datensätze sollen erstellt werden? ";ra$'
INPUT " ",z%
l%=(z%)*75
PRINT AT(30,12);"Erstelle Datensatz Nr. "
FOR n%=0 TO z%-1
PRINT AT(53,12);n%'
d$(n%,0)="Datensatz "+RIGHT$("...."+STR$(n%),5)
d$(n%,1)=STRING$(10,RANDOM(26)+65)
d$(n%,2)=">20-Byte-Datenfeld< "
d$(n%,3)=STRING$(8,RANDOM(10)+48)
d$(n%,4)=LEFT$(STRING$(6,STR$(n%)+"-"),12)
d$(n%,5)="Schlußfeld"
NEXT n%
RETURN