DBF - low-level file reading

Moderator: Moderatoren

Antworten
Benutzeravatar
azzo
1000 working lines a day
1000 working lines a day
Beiträge: 520
Registriert: So, 28. Mär 2010 19:21
Hat sich bedankt: 3 Mal
Danksagung erhalten: 17 Mal

DBF - low-level file reading

Beitrag von azzo »

Hallo Freunde,

Ich arbeite jetzt schon lange mit mod_harbour. Es gibt hier eine verzögerte Serveranforderung von durchschnittlich 150 ms beim Starten. Dann läuft alles sehr schnell. Ich habe nun interessehalber Tests mit einem einfachen "Hello World" mit PHP und mod_harbour durchgeführt. PHP ist sehr schnell.

Bei großen Programmen fällt das nicht wirklich auf. Aber wenn man schnell ein kleines Programm öffnet und warten muss, ist das ärgerlich. In diesem Fall ist die Toleranz gering.

Ich habe deshalb die Fetch-Anfrage auf einen PHP-Endpoint programmiert. Da ich ja mit DBF arbeite, versuchte ich es mit einer selbstgeschriebenen Low-Level-Funktion. Die Performance in Datenbankgrößen, mit denen ich arbeite, funktioniert gut. Hier ein Link:
https://www.modharbour.club/workschedule/kundentbl.html

Dateigröße: 200000 Datensätze

Inzwischen habe ich die PHP-Funktionen auch nach harbour übersetzt, und dort funktioniert es ebenfalls gut.

Code: Alles auswählen

//prg
#include "FiveWin.ch"

static nStartTime, nDuration

PROCEDURE Main
    LOCAL cFilePath := "c:\fwh\samples\Data\database.dbf"
    LOCAL cName := "clark"
    LOCAL aResult

    aResult := FindNameInDbf(cFilePath, cName)

RETURN

FUNCTION FindNameInDbf(cFilePath, cName)
    LOCAL nHandle := FOPEN(cFilePath)
    LOCAL cHeader := SPACE(32)
    LOCAL nHeaderSize, nRecordSize, nNumRecords
    LOCAL aFieldDescriptors := {}
    LOCAL aFieldOffsets := {}
    LOCAL nOffset := 0
    LOCAL cFieldDescriptor, cFieldName
    LOCAL nFieldLength
    LOCAL nNameOffset, nNameLength
    LOCAL aMatchingRecords := {}
    LOCAL cRecord, cExtractedName 
    LOCAL hField, hRecordData
    LOCAL i, j
    LOCAL cFieldValue
    LOCAL hFieldDescriptor := { => }
    LOCAL nFound := 0
    LOCAL cFileData
	 LOCAL nVersion
 	 LOCAL nYear
 	 LOCAL nMonth
 	 LOCAL nDay
	 LOCAL LastUpdate
	 
	 
    Msginfo("Start Suche")
    nStartTime := SECONDS()

    IF nHandle == -1
        ? "Konnte die Datei nicht öffnen."
        RETURN {}
    ENDIF

    // Read entire file into memory
    cFileData := MEMOREAD(cFilePath)

    // Header lesen
    cHeader := LEFT(cFileData, 32)
    
    
     nVersion := ASC(LEFT(cHeader, 1))

    // Display human-readable format based on the version number
    DO CASE
        CASE nVersion == 0x02
           nVersion :=  "Version: FoxBASE"
        CASE nVersion == 0x03
            nVersion :=  "Version: dBASE III PLUS (no memo)"
        CASE nVersion == 0x30
            nVersion :=  "Version: Visual FoxPro"
        CASE nVersion == 0x31
            nVersion :=  "Version: Visual FoxPro (with AutoIncrement field)"
        CASE nVersion == 0x43
            nVersion :=  "Version: dBASE IV SQL table files, no memo"
        CASE nVersion == 0x63
            nVersion :=  "Version: dBASE IV SQL system files, no memo"
        CASE nVersion == 0x83
           nVersion :=  "Version: FoxBASE+/dBASE III PLUS, with memo"
        CASE nVersion == 0x8B
            nVersion :=  "Version: dBASE IV with memo"
        CASE nVersion == 0xCB
            nVersion :=  "Version: dBASE IV SQL table files, with memo"
        CASE nVersion == 0xF5
            nVersion :=  "Version: FoxPro 2.x (or earlier) with memo"
        CASE nVersion == 0xE5
            nVersion :=  "Version: HiPer-Six format with SMT memo files"
        CASE nVersion == 0xFB
            nVersion :=  "Version: FoxBASE"
        OTHERWISE
           nVersion :=  "Unknown DBF version"
    ENDCASE
 	
 	nYear := ASC(SUBSTR(cHeader, 2, 1)) + 1900  // Year (YY)
 	nMonth := ASC(SUBSTR(cHeader, 3, 1))        // Month (MM)
 	nDay := ASC(SUBSTR(cHeader, 4, 1))          // Day (DD)

 	//	? "Last Update Date:", STR(nYear, 4) + "-" + TRANSFORM(nMonth, "@Z 99") + "-" + TRANSFORM(nDay, "@Z 99")  // Display the date in YYYY-MM-DD format
	LastUpdate := STR(nYear, 4) + "-" + TRANSFORM(nMonth, "@Z 99") + "-" + TRANSFORM(nDay, "@Z 99")
    
    // Byte-Interpretation der Header-Daten
    nNumRecords := (ASC(SUBSTR(cHeader, 5, 1)) + (ASC(SUBSTR(cHeader, 6, 1)) * 256) + (ASC(SUBSTR(cHeader, 7, 1)) * 65536) + (ASC(SUBSTR(cHeader, 8, 1)) * 16777216))
    nHeaderSize := (ASC(SUBSTR(cHeader, 9, 1)) + (ASC(SUBSTR(cHeader, 10, 1)) * 256))
    nRecordSize := (ASC(SUBSTR(cHeader, 11, 1)) + (ASC(SUBSTR(cHeader, 12, 1)) * 256))

    // Felddeskriptoren lesen
    FOR i := 33 TO nHeaderSize STEP 32
        cFieldDescriptor := SUBSTR(cFileData, i, 32)
        IF ASC(LEFT(cFieldDescriptor, 1)) == 13
            EXIT
        ENDIF
        cFieldName := RTRIM(SUBSTR(cFieldDescriptor, 1, 11))
        nFieldLength := ASC(SUBSTR(cFieldDescriptor, 17, 1))
        AADD(aFieldDescriptors, { "name" => cFieldName, "length" => nFieldLength })
    NEXT

    // Feld-Offsets berechnen
    FOR i := 1 TO LEN(aFieldDescriptors)
        hFieldDescriptor := aFieldDescriptors[i]
        AADD(aFieldOffsets, { hFieldDescriptor["name"], nOffset, hFieldDescriptor["length"] })
        nOffset += hFieldDescriptor["length"]
    NEXT
   
    nNameOffset := AScan(aFieldOffsets, { |a| LEFT(a[1], 10) = "LAST" })
    nNameLength := aFieldOffsets[nNameOffset, 3]

    // Process records
    FOR i := 1 TO nNumRecords
		 cRecord := SUBSTR(cFileData, nHeaderSize + (i - 1) * nRecordSize + 1, nRecordSize)
       cExtractedName :=   ALLTRIM(LOWER( SUBSTR(cRecord, aFieldOffsets[nNameOffset, 2] + 1, nNameLength) ))
      
      
      
    //   IF cExtractedName = cName 
            nFound += 1

            hRecordData := { "recno" => i }
            nOffset := 0

            FOR j := 1 TO LEN(aFieldDescriptors)
                hField := aFieldDescriptors[j]
                cFieldValue := RTRIM(SUBSTR(cRecord, nOffset + 2, hField["length"]))
                hRecordData[hField["name"]] := cFieldValue
                nOffset += hField["length"]
            NEXT

            AADD(aMatchingRecords, hRecordData)
            
            if nFound > 50
            EXIT
            ENDIF
   //     ENDIF
    NEXT

    nDuration := (SECONDS() - nStartTime) * 1000 // Dauer in Millisekunden
    xbrowse(aMatchingRecords, "LastUpdate  "+ LastUpdate + "   NumRecords"+ str(nNumRecords) + "   Searchtime: " + STR(nDuration, 10, 2) + " ms  Records found: " + STR(nFound))

    RETURN NIL
 
Hilfsprogramm zum Erstellen der Datenbank.

Code: Alles auswählen


 #include 'fivewin.ch' 

//----------------------------------------------------------------------------//

REQUEST DBFCDX
REQUEST DBFFPT
//----------------------------------------------------------------------------//


function Main() 



local aFields := { { "STAFFCODE", "C", 20, 0 },;
						 { "FNAME", "C", 20, 0 },;
                   { "QUALIF", "C", 5, 0 },;
                   { "EMAIL", "C", 45, 0 },;
                   { "MATCHCODE", "C", 100, 0 } }

DbCreate( "staff.dbf", aFields, "DBFCDX" )



//DbCreate(".\data\dbf1.dbf",aFields  )

  ? "Erstellt"

RETURN NIL

INIT PROCEDURE PrgInit

   SET CENTURY ON
   SET EPOCH TO YEAR(DATE())-98

   SET DELETED ON
   SET EXCLUSIVE OFF

   REQUEST HB_Lang_DE
   HB_LangSelect("DE")
   SET DATE TO GERMAN
   SetHandleCount(205)
   rddsetdefault( "DBFCDX" )
   SetGetColorFocus()

   EXTERN DESCEND

   SetBalloon( .F. )
   SetKinetic( .T. )

   fwNumFormat( 'E', .t. )

RETURN

//----------------------------------------------------------------------------//

Zum Lesen von 50 Dateneinträgen, wie in deinem Testbeispiel gepostet, beträgt die Zeit 14 ms, was es zur kürzesten in dieser Testreihe macht.

Ich habe mich schon lange nicht mehr mit der internen Struktur des DBF-Formats beschäftigt.

Ich konnte mir kaum vorstellen, dass es so einfach aufgebaut ist. So viel unnötige Verpackung, die im Laufe der Zeit beigepackt wurde. Es ist klar, dass Datenzugriff über Netzwerk (Fileserver) mehr Anforderungen stellt, aber am Webserver, wo nur der Server auf die lokalen Daten am gleichen PC zugreift, funktioniert das gut.

Word, Excel, Access haben auch keine Dateisperren, sondern erstellen parallele Sperrdateien. Ich mache es genauso.

Vielleicht interessiert es jemanden.

LG

Otto

PS: Falls jemand am PHP-source interessiert ist:
https://www.modharbour.club/workschedul ... tabase.zip
Benutzeravatar
azzo
1000 working lines a day
1000 working lines a day
Beiträge: 520
Registriert: So, 28. Mär 2010 19:21
Hat sich bedankt: 3 Mal
Danksagung erhalten: 17 Mal

Re: DBF - low-level file reading

Beitrag von azzo »

Hallo Freunde,

ich habe das Programm nun noch etwas erweitert und kann jetzt jede DBF-Datei mit dem DOS-Pfad eingeben und im Webbrowser anzeigen und konfigurieren.

Der DBF Reader und Konfigurationstool ist eine Webanwendung, die es Anwendern ermöglicht, Daten aus DBF-Datenbanken zu lesen, zu konfigurieren und in einem benutzerfreundlichen Interface anzuzeigen. Die Anwendung unterstützt die Anpassung der angezeigten Daten durch eine intuitive Konfigurationsoberfläche, in der Benutzer die sichtbaren Spalten auswählen und deren Darstellung anpassen können.
LG
Otto


Bild
Antworten