← ST-Computer 12 / 1988

Schnelle Dateien

Programmierpraxis

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.

Bild 1: Speicherung auf RAM-DISK

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.

Bild 2: Speicherung auf Diskette
' ################################# ' # 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
Franz Schott