Schnelle Dateien

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
Aus: ST-Computer 12 / 1988, Seite 90

Links

Copyright-Bestimmungen: siehe Über diese Seite