csv Datei rückwärts lesen

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • csv Datei rückwärts lesen

    Hallo zusammen

    Ich möchte folgendes machen:
    Ich habe eine CSV Datei, welche sehr gross ist. Darin sind die Börsenkurse für jeden Tag sein ca. 15 - 20 Jahren gespeichert. Nun möchte ich diese Auslesen. Das mache ich folgendermassen:
    PHP-Code:
    while (($data fgetcsv($handle1000",")) !== false) {
    //...

    Nun habe ich das Problem, dass die Daten nach Datum absteigend in der CSV Datei stehen, ich sie aber in genau umgekehrter Reihenfolge in meinem Programm brauche. Die CSV Datei schreibe ich nicht selbst!

    Ich könnte die ganze Datei in ein Array wandeln, dieses Umkehren und dann das gesammte Array wieder durchlaufen. Aber nach meinen Einschätzungen braucht das noch länger als es onehin schon braucht.

    Desshalb die Frage, gibt es eine Alternative eine Datei von hinten nach vorne zu lesen?
    Bücher für Programmierer

  • #2
    Zitat von andygyr Beitrag anzeigen
    ... Desshalb die Frage, gibt es eine Alternative eine Datei von hinten nach vorne zu lesen?
    Keine fertig gebackene (sprich in der PHP-Standard-Library) und meines Wissens auch keine anderswo (PEAR, phpclasses.org, e.t.c.).

    Siehe auch: read csv backwards php - Google-Suche

    Zeilen "rückwärts" einzulesen ist nicht so trivial, wie es auf den ersten Blick scheint:

    http://metacpan.org/module/File::ReadBackwards
    php - Read a file backwards line by line using fseek - Stack Overflow

    Zitat von andygyr Beitrag anzeigen
    Ich könnte die ganze Datei in ein Array wandeln, dieses Umkehren und dann das gesammte Array wieder durchlaufen. Aber nach meinen Einschätzungen braucht das noch länger als es onehin schon braucht.
    Was ist dir wichtiger? Laufzeit- oder Speicherverbrauchsoptimierung? Wenn alle Zeilen (Datensätze) zweimal in den Hauptspeicher passen, ist das Einlesen und Umsortieren die schnellste Variante. Nur falls der Hauptspeicher nicht reicht, musst du das Umsortieren auf den Massenspeicher auslagern, bspw. als SQLite-Datenbank-Tabelle (die kann man auch "rückwärts" auslesen).

    Die ReverseFileReader-Klasse nutzt einen netten Trick: Zuerst werden die Zeilenumbrüche (vorwärts) eingelesen. Anschließend kann man auf diese Daten zurückgreifen und per fseek() und Co. rückwärts Zeile um Zeile "richtig" einlesen. Das spart Hauptspeicher, ist aber sehr langsam.
    Zuletzt geändert von fireweasel; 26.01.2014, 14:23. Grund: Zeilenumbruch in url für ReverseFileReader-Klasse entfernt
    Klingon function calls do not have “parameters”‒they have “arguments”‒and they always win them!

    Kommentar


    • #3
      Habe mal einen Test gemacht, wenn Du in etwa die Anzahl der Zeilen weißt, dann könnte es so gehen. Im Test, Anzahl der Zeilen in der CSV 1920, davon nur die letzten 120 Zeilen einlesen. Erste Schleife zählt nur hoch, um den Dateizeiger zu positionieren, bevor die zweite Schleife mit der Arbeit beginnt und nur die letzten 120 Zeilen in den Array einspeist, der dann rückwärts ausgelesen werden kann.


      PHP-Code:
      <?php

      $abz 
      1;
      $csv "testpost.csv";

      if ((
      $handle fopen($csv"r")) !== false) {

          while ((
      fgets($handle4096)) !== false) {

              
      $abz++;
              if (
      $abz 1800) {

                  while ((
      $data fgetcsv($handle4096";")) !== false) {

                      
      $abz++;
                      
      $num count($data);
                      
      $daten[] = " - ".$abz."<br>\n"// steht in der Ausgabe unter dem jeweiligen Datensatz!

                      
      for ($i 0$i $num$i++) {
                      
      $daten[] = $data[$i];
                      }
                  }
              }
          }
          
      fclose($handle);
      }

      $daten array_reverse($daten);

      foreach (
      $daten as $ausgabe) {

          echo 
      $ausgabe."<br>\n";
      }
      ?>
      Ein zweiter Test, CSV mit 51.814 Zeilen, ausgelesen Zeile 51.702 bis 51.814, war genauso schnell wie der erste Test.

      Laufzeit unter Localhost bei 51.814:

      Mit einer Schleife und die letzten 120 ausgeben: 5.9 Sekunden
      Mit zwei Schleifen und die letzten 112 ausgeben: 0.1 Sekunden

      Wenn ich die Zeilen vorher zähle, erhöht sich die die Laufzeit auf 0.2 bis 0.3 Sekunden. Von unset musste ich bei einer Schleife Gebrauch machen, da memory size überschritten, bei zwei hätte ich drauf verzichten können.

      PHP-Code:
      $abz 1;
      $csv "testpost2.csv";

      $zeilen  file($csv);
      $abzeile count($zeilen) - 120;
      unset (
      $zeilen);

      if ((
      $handle fopen($csv"r")) !== false) {

          while ((
      fgets($handle4096)) !== false) {

              
      $abz++;
              if (
      $abz $abzeile) { ... 
      Zuletzt geändert von Melewo; 15.03.2013, 11:13.

      Kommentar


      • #4
        Zitat von Melewo Beitrag anzeigen
        ...

        Wenn ich die Zeilen vorher zähle, erhöht sich die die Laufzeit auf 0.2 bis 0.3 Sekunden. Von unset musste ich bei einer Schleife Gebrauch machen, da memory size überschritten, bei zwei hätte ich drauf verzichten können.

        PHP-Code:
        $abz 1;
        $csv "testpost2.csv";

        $zeilen  file($csv);
        $abzeile count($zeilen) - 120;
        unset (
        $zeilen);

        if ((
        $handle fopen($csv"r")) !== false) {

            while ((
        fgets($handle4096)) !== false) {

                
        $abz++;
                if (
        $abz $abzeile) { ... 
        Ähemm ..., dir ist schon klar, dass file() die gesamte Datei einliest und ein Array von Zeilen daraus bastelt? In dem Moment hast du die komplette Tabelle im Hauptspeicher. Dann kannst du entweder rückwärts über das Array iterieren (geht allerdings nicht mit foreach()) oder vorher das Array umkehren. Aber das nochmalige Einlesen jeder Zeile mit fgets()/fgetcsv()/e.t.c. ist in dem Moment unnötig. Die Datensätze sind ja schon alle eingelesen worden. Sie müssen nur noch dekodiert werden.
        Klingon function calls do not have “parameters”‒they have “arguments”‒and they always win them!

        Kommentar


        • #5
          Danke für eure Antworten.

          Ich will die gesammte Datei auslesen, nicht nur die untersten paar Zeilen. Auch weiss ich nicht wie viele Zeilen die Gesamte Datei hat, dafür müsste ich für jede Aktie die Handelstage bis zum Börsenstart zurückrechnen..

          Ist schade dass es keine direkte PHP Funktion für das gibt. Ich habe nun eine Aktie gewählt welche seit 1962 an der Börse ist. Das sind rund 12'800 Zeilen. (Für jeden Handelstag eine Zeile)
          Wenn ich da die Daten in ein Array lese, das Array umdrehe und mit einem foreach durchgehe und ausgebe, inkl. str_getcsv und print_r brauche ich dafür ca. 5 Sekunden, vobei die dauer für print_r in etwa meinem Vorhaben entsprechen wird.

          Leider werden die anzahl Handelstage auch nicht weniger sondern immer mehr.
          Bücher für Programmierer

          Kommentar


          • #6
            Zitat von andygyr Beitrag anzeigen
            Desshalb die Frage, gibt es eine Alternative eine Datei von hinten nach vorne zu lesen?
            Weiß ja nicht, wie groß Deine CSV ist oder werden könnte. Der Einwand von fireweasel war berechtigt. Deshalb noch einmal einen Test gemacht, diesmal mit einer CSV mit 200.001 Zeilen und einer Größe von 181 MB (190.381.377 Bytes). Bei Verwendung von File kommt nun sofort ein Abbruch.

            PHP-Code:
            <?php

            $csv  
            "test3.csv";
            $daten file($csv);

            // Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 190389569 bytes)
            ?>
            Aber mit Schleifen, wobei bei den Tests nur die letzten 20 Zeilen eingelesen und ausgegeben wurden, klappt es ohne Probleme. Laufzeit: 0.6 Sekunden

            PHP-Code:
            <?php

            $abz  
            1;
            $rows 0;
            $asg  20;
            $csv  "test3.csv";

            if ((
            $handle fopen($csv"r")) !== false) {

                while ((
            fgets($handle4096)) !== false ) {

                
            $rows++;
                }
                
            $abzeile $rows $asg;
                
            rewind ($handle);

                while ((
            fgets($handle4096)) !== false) {

                    
            $abz++;
                    if (
            $abz $abzeile) {

                        while ((
            $data fgetcsv($handle4096";")) !== false) {

                            
            $num count($data);
                            
            $daten[] = $data;
                        }
                    }
                }
                
            fclose($handle);
            }

            $daten array_reverse($daten);

            echo 
            "<ol>\n";

            for (
            $i 0$i $asg$i++) {

                echo 
            "<li>\n<ul>\n";

                for (
            $n 0$n $num$n++) {

                    echo 
            "<li>".htmlspecialchars($daten[$i][$n])."</li>";
                }
                echo 
            "</ul>\n</li>\n";
            }
            echo 
            "</ol>\n";

            ?>

            Kommentar


            • #7
              Zitat von andygyr Beitrag anzeigen
              Ich will die gesammte Datei auslesen, nicht nur die untersten paar Zeilen. Auch weiss ich nicht wie viele Zeilen die Gesamte Datei hat, dafür müsste ich für jede Aktie die Handelstage bis zum Börsenstart zurückrechnen.
              Da haben sich unsere Posts gerade überschnitten, die Zeilen werden ja nun gezählt und ein von-bis Bereich lässt sich ja über if ($abz > $abzeile) entsprechend der gewünschten Ausgabe einstellen, dann halt nicht mehr von abzeile, sondern abzeile biszeile, wie bei einer Blätterfunktion zum Weiterblättern von Seite zu Seite oder von Zeitraum bis Zeitraum.
              Zuletzt geändert von Melewo; 15.03.2013, 17:23.

              Kommentar


              • #8
                Hi @all,

                warum so kompliziert? Man kann einen Zähler auch dekrementieren. Heißt, wenn ich die Datei gelesen habe . . .
                PHP-Code:
                <?php
                /*
                * Beispiel mit einer Textdatei
                */
                $f=fopen("doku.txt","r");
                while(
                $line=fgets($f)){
                 
                $line_arr[]=$line;
                }
                fclose($f);
                ?>
                . . . gebe ich die letzte Zeile, count(Array), zuerst aus, und zähle bis Null runter . . .

                PHP-Code:
                for($i=count($line_arr);$i>=0;$i--){
                 echo 
                'Zeile '.$i.': '.$line_arr[$i].'<br>';

                Gruß
                Günni

                Kommentar


                • #9
                  Nun habe ich da noch einmal einiges getestet. Zwei Problem, wobei das eine Problem eventuell vernachlässigt werden könnte, wenn es sich um keine Mammutdateien handelt. fgetcsv ist wesentlich langsamer als fgets.

                  Beispiel 1:
                  PHP-Code:
                  $f=fopen("test4.csv","r");

                  while ((
                  $line fgets($f4096)) !== false) {

                      
                  $line_arr[]=$line;
                  }
                  fclose($f);

                  // Laufzeit: 0.1 
                  Beispiel 2:
                  PHP-Code:
                  $f=fopen("test4.csv","r");

                  while ((
                  $line fgetcsv($f4096";")) !== false) {

                      
                  $line_arr[]=$line;
                  }
                  fclose($f);

                  // Laufzeit: 5.9 Sekunden 
                  Die erste Variante, die von Guenni61, im Zusammenspiel mit str_getcsv war eigentlich verhältnismäßig schnell.

                  PHP-Code:
                  $iz count($line_arr);

                  for (
                  $i $iz -1$i >= 49980$i--){

                       
                  $felder str_getcsv($line_arr[$i], ";");
                       
                       
                  print_r($felder);

                  Nur wenn es sich um eine Mammutdatei handelt, versagt die, da ja alles mit $line_arr[]=$line; in einem Array gespeichert wird und dann nur diese Fehlermitteilung ausgegeben wird:

                  Fatal error: Allowed memory size of 134217728 bytes exhausted

                  Bei den von mir verwendeten Testdateien funktionierte es bei der mit 50.000 Zeilen noch problemlos, bei der mit 200.000 Zeilen kam die Meldung.

                  @ andygyr

                  Je nachdem, wie viele Felder und Daten Deine Dateien enthalten, müsstest Du mal ausprobieren, ob Du mit einer Überschreitung des Limits rechnen musst oder auch nicht. Falls ja, habe zwischenzeitlich mit einem kleinen Tutorial begonnen:

                  OOP: Klasse zum Auslesen (rückwärts) von umfangreichen CSV-Dateien | Coder-Welten.de

                  Enthält dann auch eine Blätterfunktion usw..

                  Kommentar


                  • #10
                    Wozu umständlich ein eigenes Pseudo-Datenbanksystem entwickeln, anstatt einfach die CSV-Datei in eine SQL-Datenbank zu importieren? Hier kannst du dann problemlos gezielt auf die gewünschten Einträge zugreifen, sortieren, filtern, usw. usf. Man muss sich das Leben ja nicht künstlich schwer machen.

                    Kommentar


                    • #11
                      Das hatte ich mir von Anfang gedacht, wäre doch die beste Lösung. Weiß ja nicht die Gründe, doch eigentlich gibt es ja langfristig kaum einen besseren Weg, als alles in eine Datenbank zu importieren.

                      Kommentar


                      • #12
                        Zitat von Melewo Beitrag anzeigen
                        ...
                        Falls ja, habe zwischenzeitlich mit einem kleinen Tutorial begonnen:

                        OOP: Klasse zum Auslesen (rückwärts) von umfangreichen CSV-Dateien | Coder-Welten.de ...
                        Nett. Es gibt aber einen kleinen Haken. Verwendest du fgets() mit Angabe der Puffer-Größe, bricht die Funktion unter Umständen ab, bevor ein Zeilenumbruch gelesen wurde. Das dürfte bei CSV-Dateien dazu führen, dass dieser und der nächste Datensatz unbrauchbar werden.

                        Zitat von andygyr Beitrag anzeigen
                        ... Ist schade dass es keine direkte PHP Funktion für das gibt. ...
                        Dafür sind Programmiersprachen im Allgemeinen da: um sich seine eigenen Funktionen, zugeschnitten auf die konkrete Aufgabe, zu zimmern.

                        ... rund 12'800 Zeilen. (Für jeden Handelstag eine Zeile)
                        Wenn ich da die Daten in ein Array lese, das Array umdrehe und mit einem foreach durchgehe und ausgebe, inkl. str_getcsv und print_r brauche ich dafür ca. 5 Sekunden, vobei die dauer für print_r in etwa meinem Vorhaben entsprechen wird.
                        Das Einlesen von Festplatten dauert seine Zeit, besonders, wenn es zeilenweise geschieht: Intern holt fgets() die Daten Byte für Byte statt in Blöcken, weil es jedesmal nachschaut, wann ein Zeilenumbruch-Zeichen kommt.

                        Auch das "Umwenden" des Arrays ist zeitaufwändig. Wie du schon zweimal gesagt bekommen hast, ist das nicht unbedingt nötig: Du kannst ein Array auch von hinten nach vorne durchlaufen.

                        Und die Ausgabe (im Browser?) kann auch dauern. Da die Menge der Daten nicht von vornherein bekannt, werden Puffer reserviert, gefüllt und Daten hin- und herkopiert.

                        Um herauszubekommen, wo du den Vorgang optimieren kannst, teste mal die Laufzeit einmal ohne Ausgabe mit print_r() und einmal ohne das Array umzukehren.

                        Leider werden die anzahl Handelstage auch nicht weniger sondern immer mehr.
                        Langfristig wird dir daher jeder zu einer echten Datenbank (statt CSV-Files) raten. Da kannst du die Datensätze in der vorliegenden Reihenfolge ein- und zur Auswertung andersherum wieder auslesen ("... order by <timestamp> desc").

                        Für den Anfang tut es SQLite, das kann seine Datenbanken auf Anforderung komplett im Hauptspeicher halten, so lange sie dort reinpassen.
                        Klingon function calls do not have “parameters”‒they have “arguments”‒and they always win them!

                        Kommentar


                        • #13
                          Zitat von fireweasel Beitrag anzeigen
                          Es gibt aber einen kleinen Haken. Verwendest du fgets() mit Angabe der Puffer-Größe, bricht die Funktion unter Umständen ab, bevor ein Zeilenumbruch gelesen wurde.
                          Du kannst vielleicht bei fgets auf eine Angabe von length verzichten, nicht aber bei fgetcsv, da das Trennzeichen ja erst an dritter Position angegeben wird. Und ohne Angabe von Größe und Trennzeichen macht fgetcsv nur Fehler und weiß nicht mehr wo ein Feld aufhört und das andere anfängt.

                          Kommentar


                          • #14
                            Zitat von Melewo Beitrag anzeigen
                            Du kannst vielleicht bei fgets auf eine Angabe von length verzichten, nicht aber bei fgetcsv, da das Trennzeichen ja erst an dritter Position angegeben wird.
                            PHP: fgetcsv - Manual:
                            length

                            Must be greater than the longest line (in characters) to be found in the CSV file (allowing for trailing line-end characters). It became optional in PHP 5. Omitting this parameter (or setting it to 0 in PHP 5.0.4 and later) the maximum line length is not limited, which is slightly slower.
                            I don't believe in rebirth. Actually, I never did in my whole lives.

                            Kommentar


                            • #15
                              array fgetcsv ( resource $handle [, int $length = 0 [, string $delimiter = ','
                              Wenn length weggelassen wird, so findet fgetcsv auch delimiter nicht mehr, weil fgetcsv delimiter an dritter Position erwartet. Habe es doch probiert, aus 6 Feldern machte fgetcsv dann 2.
                              PHP-Code:
                                Fehlerfrei:

                                while ((
                              $data fgetcsv($handle0";")) !== false) {
                                while ((
                              $data fgetcsv($handle4096";")) !== false)
                                 

                                
                              Error:

                                while ((
                              $data fgetcsv($handle, , ";")) !== false) {
                                 

                                
                              Fehlerhaftmacht aus 6 Feldern 2 Felder:

                                while ((
                              $data fgetcsv($handle";")) !== false) {
                                while ((
                              $data fgetcsv($handle)) !== false) { 

                              Kommentar

                              Lädt...
                              X