glucke: daten einlesen

Guten Abend,

mit PHP lese ich eine Datei ein, die folgende (hier stark vereinfachte) Struktur hat:

ere,cer,23d,ad23,332;dde,343,234,2343,lldo;wert1,wert2,wert3,wert4,wert5; etc

Das heisst, im Prinzip eine kommaseparierte Datei (im Prinzip deshalb, weil die Trenner variieren können).

Durch einen implode am Semikolon werden die einzelnen Werte in einen Array verfrachtet, dessen Elemente wiederum aufspaltet, ausgewertet und schlussendlich in anderer, modifizierter Form in eine MySQL-Datenbank geschrieben werden.

Von der grundsätzlichen Vorgehensweise ein triviales Problem. Die Modifikationen bzw. Berechnungen sollen an dieser Stelle nicht erwähnt werden, da sie für die weiter unten beschriebene Problemstellung unbedeutend sind.

Das Problem:

Diese Datei kann sehr groß werden, d.h. ich rechne mit einer Dateigröße von > 1GB, kann gut auch das zehnfache sein. Diese Datei kann ich natürlich in der Größe nicht einlesen und dann weiter verarbeiten. Ich muss wohl entweder eine Art Stream nutzen oder den Dateizeiger entsprechend setzen (falls das mit PHP überhaupt möglich sein sollte).

Die Frage:

Wie würde ein Programmierer dieses Problem am sinnvollsten lösen?

  1. Hallo,

    fopen()
    fread()
    fclose()

    und vielleicht auch fgetcsv()?

    Gruß

    jobo

  2. Hi,

    mit PHP lese ich eine Datei ein, die folgende (hier stark vereinfachte) Struktur hat:

    ere,cer,23d,ad23,332;dde,343,234,2343,lldo;wert1,wert2,wert3,wert4,wert5; etc

    Das heisst, im Prinzip eine kommaseparierte Datei (im Prinzip deshalb, weil die Trenner variieren können).

    Geht das immer so weiter, oder kommt da auch mal Zeilenumbruch? (Und wenn ja, welche Bedeutung hat er, wenn offenbar das Semikolon schon der Datensatztrenner zu sein scheint, da du an diesem zuerst aufsplittest.)

    Das Problem:

    Diese Datei kann sehr groß werden, d.h. ich rechne mit einer Dateigröße von > 1GB, kann gut auch das zehnfache sein. Diese Datei kann ich natürlich in der Größe nicht einlesen und dann weiter verarbeiten.

    Nicht in einem "Happen" - aber zeilenweises einlesen sollte auch bei grossen Dateien nicht allzu unperformant sein.

    Wenn man nicht zeilenweise einlesen kann (siehe Nachfrage oben) - dann muss man sich im zweifelsfalle selbst was basteln, was eine bestimmte Anzahl Bytes einliest, und schaut, wo diese zu splitten sind - unter "Übertrag" durch das auf Bytezahl begrenzende Einlesen bedingter "zerschnittener" Feldinhalte an den nächsten Durchlauf.

    Die Frage:

    Wie würde ein Programmierer dieses Problem am sinnvollsten lösen?

    Er würde nach exakteren Vorgaben und Spezifikationen fragen.

    Wenn
    MfG ChrisB

    --
    Light travels faster than sound - that's why most people appear bright until you hear them speak.
    1. Hallo,

      danke für die Antwort!

      ere,cer,23d,ad23,332;dde,343,234,2343,lldo;wert1,wert2,wert3,wert4,wert5; etc

      Geht das immer so weiter, oder kommt da auch mal Zeilenumbruch? (Und wenn ja, welche Bedeutung hat er, wenn offenbar das Semikolon schon der Datensatztrenner zu sein scheint, da du an diesem zuerst aufsplittest.)

      Nein, es gibt keinen Zeilenumbruch.

      Wenn man nicht zeilenweise einlesen kann (siehe Nachfrage oben) - dann muss man sich im zweifelsfalle selbst was basteln, was eine bestimmte Anzahl Bytes einliest, und schaut, wo diese zu splitten sind - unter "Übertrag" durch das auf Bytezahl begrenzende Einlesen bedingter "zerschnittener" Feldinhalte an den nächsten Durchlauf.

      gut, so etwas in der Art dachte ich mir schon. Das ist ein guter Hinweis. ich will nicht völlig unsinniges Zeug machen, wenn es auch einfacher gegangen wäre.

      e:

      Wie würde ein Programmierer dieses Problem am sinnvollsten lösen?

      Er würde nach exakteren Vorgaben und Spezifikationen fragen.

      Hmm, wie gesagt, es gibt diese Datei mit der beschriebenen Struktur, die eingelesen und verarbeitet werden muss. Überdies bin ich auf PHP5 angewiesen.

      Glucke

      1. Hallo,

        Geht das immer so weiter, oder kommt da auch mal Zeilenumbruch? (Und wenn ja, welche Bedeutung hat er, wenn offenbar das Semikolon schon der Datensatztrenner zu sein scheint, da du an diesem zuerst aufsplittest.)
        Nein, es gibt keinen Zeilenumbruch.

        Schade das fgets nur den Zeilenumbruch als Seperator kennt. Da ist Perl klar im Vorteil ;-). Mit dieser C-Style Funktion kann man sich das aber selber basteln:

        function getline( $fp, $delim )  
        {  
            $result = "";  
            while( !feof( $fp ) )  
            {  
                $tmp = fgetc( $fp );  
                if( $tmp == $delim )  
                    return $result;  
                $result .= $tmp;  
            }  
            return $result;  
        }
        

        Grüße
        Jasmin

        1. Moin!

          Schade das fgets nur den Zeilenumbruch als Seperator kennt. Da ist Perl klar im Vorteil ;-). Mit dieser C-Style Funktion kann man sich das aber selber basteln:

          Kann man, aber will man nicht - weil es ein wahnsinniges Verbraten von Rechenzeit darstellt, aus einer angedroht gigabytegroßen Datei die Zeichen einzeln mit fgetc() einzulesen. Das ist einfach nur ineffizient.

          Abgesehen davon:

          function getline( $fp, $delim )

          {
              $result = "";
              while( !feof( $fp ) )
              {
                  $tmp = fgetc( $fp );
                  if( $tmp == $delim )
                      return $result;
                  $result .= $tmp;
              }
              return $result;
          }

            
          Wenn der angenommene Delimitier nicht auftritt, retourniert diese Funktion, nachdem sie ihn zeichenweise im Speicher akkumuliert hat, den gesamten Inhalt der Datei. Das kann zuviel sein. Die Einschränkung bei fread(), nur maximal 8 KB Daten einzulesen, hat schon so seinen Sinn.  
            
           - Sven Rautenberg
          
          1. Hi,

            Die Einschränkung bei fread(), nur maximal 8 KB Daten einzulesen, hat schon so seinen Sinn.

            Nein, ihren :-P

            SCNR ChrisB

            --
            Light travels faster than sound - that's why most people appear bright until you hear them speak.
        2. Hi,

          Schade das fgets nur den Zeilenumbruch als Seperator kennt.

          Ja, und andere wie fgetcsv offenbar auch.

          Wenn man bei letzterer Funktion schon delimiter- und enclosure-Zeichen per Parameter bestimmten kann - dann hätte man das in Hinsicht auf den Datensatz-Trenner auch gleich flexibel halten können/sollen.

          MfG ChrisB

          --
          Light travels faster than sound - that's why most people appear bright until you hear them speak.
        3. Hello,

          Schade das fgets nur den Zeilenumbruch als Seperator kennt. Da ist Perl klar im Vorteil ;-). Mit dieser C-Style Funktion kann man sich das aber selber basteln:

          Du könntest auch gleich mit dem Stream arbeiten, wenn es sich um ein lokales File handelt.
          http://us2.php.net/manual/en/book.stream.php

          Liebe Grüße aus dem schönen Oberharz

          Tom vom Berg

          --
          Nur selber lernen macht schlau
          http://bergpost.annerschbarrich.de
  3. Hallo,

    mit PHP lese ich eine Datei ein, [...] im Prinzip eine kommaseparierte Datei (im Prinzip deshalb, weil die Trenner variieren können).

    das ist als Ausgangsmaterial suboptimal, aber vermutlich nicht zu ändern.

    Diese Datei kann sehr groß werden, d.h. ich rechne mit einer Dateigröße von > 1GB, kann gut auch das zehnfache sein.

    Oha. Da versagt natürlich jegliche Verarbeitung en bloc.

    Wie würde ein Programmierer dieses Problem am sinnvollsten lösen?

    Als Pseudocode:

    open()
      do
         read char while (not separator)
         process input
         skip separator
      while (not eof)
      close()

    So long,
     Martin

    --
    Finanztipp:
    Leihen Sie sich Geld von einem Pessimisten.
    Er rechnet sowieso nicht damit, dass er es zurückbekommt.
    1. Hallo Martin,

      was genau passiert eigentlich bei open bzw. in diesem Kontext fopen()?
      Wird da nur eine Art Zeiger auf den Dateianfang einer Datei gesetzt und diese dann zum Lesen bzw. Schreiben geöffnet? Sie wird also nicht direkt in den Speicher geladen?

      glucke

      1. Hallo,

        was genau passiert eigentlich bei open bzw. in diesem Kontext fopen()?

        http://de.php.net/manual/de/function.fopen.php

        Wird da nur eine Art Zeiger auf den Dateianfang einer Datei gesetzt und diese dann zum Lesen bzw. Schreiben geöffnet?

        Die Art Zeiger heißt Dateizeiger. Diesen kannst Du mit fseek() positionieren, was erforderlich sein könnte, wenn Du wegen Skriptlaufzeitbeschränkungen die Datei nicht in einem Rutsch verarbeiten kannst.

        Sie wird also nicht direkt in den Speicher geladen?

        Nein, warum sollte sie? Wie Chris bereits schrieb, wie Martin andeutete, kannst Du bestimmen, wieviel Du einlesen willst. Dazu nutzt man Funktionen wie zum Beispiel fgets(), fgetc(), fread().

        Freundliche Grüße

        Vinzenz

      2. Hallo,

        was genau passiert eigentlich bei open bzw. in diesem Kontext fopen()?

        da wird zunächst mal geprüft, ob die gewünschte Datei überhaupt existiert und zugänglich (Zugriffsrechte!) ist, und dann das Lesen vorbereitet.

        Wird da nur eine Art Zeiger auf den Dateianfang einer Datei gesetzt und diese dann zum Lesen bzw. Schreiben geöffnet?

        Ja. Der Vorgang ist vergleichbar damit, dass du vom Schreibtisch aufstehst, ein Buch im Regal suchst, es auf den Tisch legst und die erste Seite aufschlägst.

        Sie wird also nicht direkt in den Speicher geladen?

        Ganz bestimmt nicht. Vermutlich wird das Betriebssystem in Erwartung des folgenden Lesezugriffs schon mal die ersten paar Dateiblöcke in irgendwelche Zwischenspeicher laden[*]. Aber das ist zum Verstehen der internen Vorgänge nebensächlich.

        So long,
         Martin

        [*] Bei sehr kleinen Dateien kann das bedeuten, dass sie nach fopen() schon komplett im Arbeitsspeicher liegen. Das ist aber nicht die Regel.

        --
        Einer aktuellen Erhebung zufolge sind zehn von neun Ehefrauen eifersüchtig auf ihren Mann.
        1. Hello,

          [*] Bei sehr kleinen Dateien kann das bedeuten, dass sie nach fopen() schon komplett im Arbeitsspeicher liegen. Das ist aber nicht die Regel.

          Bei PHP werden standardmäßig 8kB cached bei fopen(name,'b')  ( binary Mode!).
          Das ist nachzulesen bei fread() http://us2.php.net/manual/en/function.fread.php

          Ich war auch der Meinung, das man die Größe des Buffers irgendwie einstellen konnte, aber das finde ich derzeit nicht.

          Liebe Grüße aus dem schönen Oberharz

          Tom vom Berg

          --
          Nur selber lernen macht schlau
          http://bergpost.annerschbarrich.de
          1. Moin!

            [*] Bei sehr kleinen Dateien kann das bedeuten, dass sie nach fopen() schon komplett im Arbeitsspeicher liegen. Das ist aber nicht die Regel.

            Bei PHP werden standardmäßig 8kB cached bei fopen(name,'b')  ( binary Mode!).
            Das ist nachzulesen bei fread() http://us2.php.net/manual/en/function.fread.php

            Aus welchem Text interpretierst du das?

            - Sven Rautenberg

            1. Hallo,

              Bei PHP werden standardmäßig 8kB cached bei fopen(name,'b')  ( binary Mode!).
              Das ist nachzulesen bei fread() http://us2.php.net/manual/en/function.fread.php
              Aus welchem Text interpretierst du das?

              vielliecht aus der Tatsache, dass da steht, fread() lese maximal 8192 Bytes am Stück? Hast Recht, fread() ist nicht fopen(), und Toms Schlussfolgerung ist damit eher eine Kurzschlussfolgerung. ;-)
              Ich hab seine Aussage nicht hinterfragt, darum ist es mir nicht aufgefallen.

              Ciao,
               Martin

              --
              F: Was macht ein Offizier, der in der Nase bohrt?
              A: Er holt das Letzte aus sich heraus.
              1. Hello,

                Bei PHP werden standardmäßig 8kB cached bei fopen(name,'b')  ( binary Mode!).
                Das ist nachzulesen bei fread() http://us2.php.net/manual/en/function.fread.php
                Aus welchem Text interpretierst du das?

                vielliecht aus der Tatsache, dass da steht, fread() lese maximal 8192 Bytes am Stück? Hast Recht, fread() ist nicht fopen(), und Toms Schlussfolgerung ist damit eher eine Kurzschlussfolgerung. ;-)
                Ich hab seine Aussage nicht hinterfragt, darum ist es mir nicht aufgefallen.

                Es ist wirklich nicht mehr einfach zu finden. Ich hatte mir das allerdings so notiert als Ergebnis einer Diskussion mit Christian Seiler...

                Wenn fopen() benutzt wird und PHP dann feststellt, dass es sich um ein lokales File handelt, wird ein Stream auf dieses File eröffnet. Der Streambuffer ist standardmäßig 8kB groß. Das suche ich nun schon die ganze Zeit.

                Für den schreibenden Zugriff ist es nochmals verifizierbar über
                http://us2.php.net/manual/en/function.stream-set-write-buffer.php
                Da steht zwar auch nur "fwrite()" aber soweit ich das überblicken kann, werden alle anderen Schreibfunktionen intern darauf zurückgeführt.

                Für fread() suche ich die Stelle nun noch.

                Außerdem müsste man vermutlich noch unterscheiden zwischen z.B. Windows und POSIX...

                Liebe Grüße aus dem schönen Oberharz

                Tom vom Berg

                --
                Nur selber lernen macht schlau
                http://bergpost.annerschbarrich.de