Große Textdateien einlesen ...

Zugriff, Engines, Konvertierung. Von ADS über DBF bis zu SQL.

Moderator: Moderatoren

Antworten
Benutzeravatar
brandelh
Foren-Moderator
Foren-Moderator
Beiträge: 15695
Registriert: Mo, 23. Jan 2006 20:54
Wohnort: Germersheim
Hat sich bedankt: 65 Mal
Danksagung erhalten: 33 Mal
Kontaktdaten:

Große Textdateien einlesen ...

Beitrag von brandelh »

Hi,

ich muss regelmäßig große Textdateien einlesen. Bisher habe ich ein bei Clipper enthaltene Funktionssammlung für zeilenweises Einlesen genutzt (CL5 Beispiel: FILEIO.PRG).
Nun brauchte ich es schneller ... die hier gezeigte Klasse liest die Daten 3 x so schnell ein wie die alte Funktion ( 7 Millionen Zeilen in 40 Sekunden ...)
Die Zeit berücksichtigt nur das Einlesen von Zeilen in den Speicher, nicht das Schreiben in eine neue DBF etc.
Die Klasse sollte auch mit UNIX (nur LF) als Zeilentrenner zurecht kommen und versehentliche chr(26) nach Blank tauschen.

Bitte testet die Klasse, damit sie robuster wird, danach stelle ich sie in die Wissensbasis ...

Code: Alles auswählen

#define TESTDATEI ...

procedure main
   local nZeile := 0
   local cZeile := ""
   local nSize, nBytes := 0
   local nDauer
   local oTR
   local nAnzLieferBG, nAnzRM
   cls
   oTR := TextReader():new( TESTDATEI )
   if oTR:FError() <> 0
      @ 1,1 say "Fehler beim Datei öffnen: "+oTR:ErrMsg()
      quit
   endif
   nSize := oTR:FSize()
   @ 1,1 say  "Testdatei sequentiell einlesen"
   @ 3,1 say  "Dateigröße      "+transform(nSize,"###,###,###,###,###")

   nAnzLieferBG := 0
   nAnzRM       := 0

   nDauer := seconds()

   do while ! oTR:EOF()
      cZeile := oTR:GetLine()
*      if len(cZeile)=0
*         exit
*      endif
      do case
         case cZeile = "LIEFER_BG"
              nAnzLieferBG++
         case cZeile = "RM_MED"
              nAnzRM++
      end case
      nZeile++
      nBytes += len(cZeile)+2 // CR+LF = 2 Byte

      if nZeile % 1000 = 0
         @ 4,1 say  "gelesene Bytes  "+transform(nBytes,"###,###,###,###,###")
         @ 5,1 say  "gelesene Zeilen "+transform(nZeile,"###,###,###,###,###")
      endif

   enddo

   oTR:Destroy()

   nDauer := seconds() - nDauer

   @ 4,1 say  "gelesene Bytes  "+transform(nBytes,"###,###,###,###,###")
   @ 5,1 say  "gelesene Zeilen "+transform(nZeile,"###,###,###,###,###")
   @ 7,1 say  "Dauer:          "+transform(nDauer,"###,###,###,###,###")+" Sekunden => "+str(nZeile/nDauer)+" Zeilen / Sekunde"
   @ 8,1 say  "                                                "+str(nZeile/nDauer/104)+" Datensätze / Sekunde"
   @10,1 say  "nAnzLieferBG:   "+transform(nAnzLieferBG,"###,###,###,###,###")
   @11,1 say  "nAnzRM:         "+transform(nAnzRM,"###,###,###,###,###")



   @13,1 say  "ENDE, bitte Taste drücken ..."

   wait

return

* Klasse zum sequentiellen Einlesen großer Dateien
* Da die Blockgröße auf 4 KB begrenzt ist, kann man auch einfach die Zeilen ausschneiden.

#include "Fileio.ch"

CLASS TextReader
  PROTECTED:
     VAR nH
     VAR nLastError
     VAR IsEOF           // Buffer hat beim Einlesen EOF erreicht, Zeilen können noch da sein !
     VAR cRest
     VAR nBufferBytes    // Anzahl der gelesenen Byte im Buffer
     VAR cCRLF, nLenCRLF // Unix/Linux nur chr(10) = 1 Byte, Windows chr(13)+chr(10) = 2 Byte
     METHOD ReadBuffer

  EXPORTED:
     METHOD Init
     METHOD Destroy
     METHOD GetLine
     METHOD GoTop
     METHOD FSize
     METHOD FError
     METHOD ErrMsg
     METHOD EOF
     METHOD IsCrLf
     METHOD IsUnix
     METHOD IsMac
ENDCLASS

METHOD TextReader:Init( cFileName )       // Öffnet die Datei zum Lesen
   ::nLastError   := 0
   ::cRest        := ""
   ::nBufferBytes := 0
   ::nH    := fopen( cFileName , FO_READ + FO_SHARED )
   if ::nH = -1
      ::nLastError := FError()
      ::IsEOF      := .t.
   else
      ::IsEOF      := .f.
      ::ReadBuffer()
      do case
         case chr(13)+chr(10) $ ::cRest // Windows etc.
              ::cCRLF    := chr(13)+chr(10)
              ::nLenCRLF := 2
         case chr(10)         $ ::cRest // Unix
              ::cCRLF    := chr(10)
              ::nLenCRLF := 1
         case chr(13)         $ ::cRest // Mac
              ::cCRLF    := chr(13)
              ::nLenCRLF := 1
      end
   endif
RETURN SELF

METHOD TextReader:Destroy()
   if ::nH <> -1
      FClose(::nH)
      ::nH := -1
   endif
   ::cRest := ""
RETURN SELF

METHOD TextReader:ReadBuffer()
   local cBuffer, nBufferLen, nBytes
   if ::nH > -1
      nBufferLen := 4096
      cBuffer    := space(nBufferLen)
      nBytes     := FRead( ::nH, @cBuffer, nBufferLen)
      cBuffer    := StrTran(cBuffer,chr(26)," ")
      ::nBufferBytes += nBytes
      if nBufferLen = nBytes // mitten in Datei
         ::cRest += cBuffer
      else
         ::cRest += left(cBuffer,nBytes)
         ::IsEOF := .t.
         if FError() <> 0
            ::nLastError := FError()
         endif
      endif
      cBuffer := ""
   endif
Return

METHOD TextReader:GetLine()
   local nPosCRLF
   local cLine := ""
   do while ! ::cCRLF $ ::cRest .and. ! ::IsEof  // Buffer einlesen, bis wir neue Zeilen haben oder die Datei gelesen wurde
      ::ReadBuffer()
   enddo
   nPosCRLF := at( ::cCRLF, ::cRest)
   if nPosCRLF > 0 // es gibt noch eine komplette Zeile, zurückgeben und kürzen
      cLine   := left(::cRest,nPosCRLF-1)
      ::cRest := substr(::cRest,nPosCRLF+::nLenCRLF)
   else
      cLine   := ::cRest
      ::cRest := ""
   endif
return cLine

METHOD TextReader:GoTop()
   if ::nH <> -1
      FSeek(::nH, 0 , FS_SET )
   endif
   ::cRest        := ""  // zwingt zum neu einlesen
   ::ReadBuffer()
return NIL

METHOD TextReader:FSize()
   local nSize := 0
   if ::nH <> -1
      nSize := FSize(::nH)
   endif
RETURN nSize

METHOD TextReader:FError()
RETURN ::nLastError

METHOD TextReader:EOF()
RETURN ::IsEOF .and. empty(::cRest)

METHOD TextReader:IsCrLf()
RETURN (::cCRLF == chr(13)+chr(10))

METHOD TextReader:IsUnix()
RETURN (::cCRLF == chr(10))

METHOD TextReader:IsMac()
RETURN (::cCRLF == chr(13))

METHOD TextReader:ErrMsg()
RETURN DosErrorMessage(::nLastError)

Gruß
Hubert
Benutzeravatar
brandelh
Foren-Moderator
Foren-Moderator
Beiträge: 15695
Registriert: Mo, 23. Jan 2006 20:54
Wohnort: Germersheim
Hat sich bedankt: 65 Mal
Danksagung erhalten: 33 Mal
Kontaktdaten:

Re: Große Textdateien einlesen ...

Beitrag von brandelh »

Ich habe oben um Mac und Unix Dateien, sowie einige Methoden (GoTop() etc.) erweitert.
Wie geschrieben, wenn jemand einen Fehler findet, bitte melden.
Gruß
Hubert
Antworten