
Übersicht » Einführung OOP
Eine CSV-Datei auszulesen und dabei die Ergebnisse zu sortieren, bereitet in der Regel keine größeren Probleme. Doch wie bei umfangreichen Dateien vorgehen, bei denen die letzten Zeilen zuerst ausgewertet werden sollen? Oder was tun, wenn die Laufzeit des Scripts zu einer für User unzumutbaren Ladezeit der Seiten führt oder gar der maximale Wert des Speichers überschritten wird, den ein Script verbrauchen darf und nur eine Fehlermeldung wie die folgende erscheint?
Fatal error: Allowed memory size of 134217728 bytes exhausted ...
Im Folgenden wird ein Script beschrieben, welches nur die Zeilen aus einer CSV-Datei einliest und in
        einem Array speichert, die für die Auswertung und Ausgabe benötigt werden und dabei mit den letzten Zeilen der jeweiligen
        CSV-Datei beginnt. Es sei angemerkt, dass es einfachere und schnellere Methoden gibt, insofern die Dateien beim Einlesen nicht
        den maximalen Speicherwert überschreiten und das Memory Limit nicht hochgesetzt werden soll oder hochgesetzt werden kann.
        
        
Für die Tests wurde eine CSV-Dateien mit 200.000 Zeilen und mit einer Gesamtgröße von 181 MB
        verwendet, wobei jede Zeile der Testdatei 6 Felder enthielt. Um die Blätterfunktion zu nutzen oder die Anzahl der pro Seite
        auszugebenen Ergebnisse zu verändern, kann der URI ein QueryString hinzugefügt werden, der je ein Parameter-Werte-Paar für
        die aufzurufende Seite und die Anzahl der Ergebnisse pro Seite enthalten kann, jedoch nicht zwingend muss.
        
        Beispiel:
"www.example.com/csv.php?seite=1&anzahl=30"
        "www.example.com/csv.php?seite=2&anzahl=30"
Im Beispiel würden bei einer CSV-Dateien mit 200.000 die Ergebnisse wie folgt ausgegeben:
Seite1: 200.000 bis 199.971
        Seite2: 199.970 bis 199.941
Die Links zum Blättern (rückwärts wie vorwärts) werden vom Script generiert. Die Anzahl der
        auszugebenen Ergebnisse müssen hingegen in dieser einfachen Version, die nur als Demo für durchgeführte Tests diente, noch
        vorgewählt werden. Soll die Auswahl durch Besuchereingaben erfolgen, könnte dem Script noch ein kleines Formular mit
        Radio-Buttons hinzugefügt werden. Ein Beispiel für ein Formular finden Sie weiter unten auf dieser Seite. Die Ausgabe der
        Zeilen erfolgt unformatiert und kann beliebig dem Verwendungszweck entsprechend angepasst werden.
        
Die Anzahl der Zeilen der jeweiligen CSV-Datei wird mit der ersten While-Schleife und fgets ermittelt.
        Da diese Schleife keine weiteren Aufgaben zu erfüllen hat, muss nach Beendigung des kompletten Durchlaufs der Dateizeiger mit
        der Funktion rewind wieder auf den Anfang des Dokumentes zurückgesetzt werden.
        
        Die zweite Schleife beginnt somit wieder bei der ersten Zeile der Datei mit dem Auszählen. Die Aufgabe dieser zweiten Schleife
        dient lediglich dem Hochzählen bis zur jeweiligen Anfangsposition. Zum Beispiel bis Zeile 199.970 bei Seite 1 oder 199.940 bei
        Seite 2, falls die CSV 200.000 Zeilen hat und 30 Ergebnisse pro Seite ausgelesen und ausgegeben werden sollen. Erst die innere
        While-Schleife beginnt im Zusammenspiel mit der Funktion fgetcsv die entsprechenden Zeilen einzulesen und in einem Array zu
        speichern. Der Vorteil besteht darin, dass nie mehr als die gewünschte Anzahl an auszugebenen Ergebnissen in dem Array
        eingespeist und in diesem gespeichert Array werden.
        Da die äußere While jedoch mindestens bis 1 zählt, muss diese ebenfalls wieder mit rewind zurückgesetzt werden, wenn die
        Zeilen 1 bis 30 (oder bis 10, 20, 50, oder bis wohin auch immer) eingelesen werden sollen, jedoch nur in diesem
        speziellen Fall. Um die gespeicherten Zeilen in der richtigen Reihenfolge auszugeben und mit den jeweils letzten Zeilen zu
        beginnen (um ein Rückwärtslesen konsequent umzusetzen), muss die Reihenfolge der Zeilen im Array abschließend noch
        mit array_reverse vertauscht werden. 
        
        Code-Listing/Script (csv-rueckwaertslesen.php):
<?php error_reporting(E_ALL); $csvdat = "test.csv"; // Die auszuwertende Datei angeben class CsvInputOutput { private $sepa = ";"; // Trennzeichen, Separator private $whi = 0; // keine Änderung erforderlich private $abz = 0; // keine Änderung erforderlich public $rows = 0; // keine Änderung erforderlich public $seite = 1; // Start beginnt mit Seite 1, falls nichts anderes übergeben wurde public $ergb = 20; // Anzahl der Ergebnisse pro Seite, falls nichts anderes übergeben wurde private $leng = 0; // kann bei CSV-Dateien ohne überlange Zeilen auf 4096 oder weniger gesetzt werden private $daten = array(); // Array für die aufzunehmenden Zeilen und Felder public $csv; /* -- Falls ein QueryString mit anzahl=wert und oder mit seite=wert übergeben wurde -- */ public function setSeite() { if (isset($_GET["seite"]) and !empty($_GET["seite"])) { $seite = trim($_GET["seite"]); $seite = preg_replace("/[^0-9]/", "", $seite); $this->seite = $seite; return $this->seite; } } public function setAnzahl() { if (isset($_GET["anzahl"]) and !empty($_GET["anzahl"])) { $anzahl = trim($_GET["anzahl"]); $anzahl = preg_replace("/[^0-9]/", "", $anzahl); $this->ergb = $anzahl; return $this->ergb; } } /* -- Die eigentliche Methode getCSV ------------------------------------------------- */ public function getCSV() { $asg2 = $this->ergb *$this->seite; if (($handle = fopen($this->csv, "r")) !== false) { while (fgets($handle) !== false ) { $this->rows++; } $abzeile = $this->rows - $asg2; rewind($handle); if ($abzeile < 0) { $restli = $abzeile + $this->ergb; $abzeile = 0; } else {$restli = $this->ergb; } while (fgets($handle) !== false) { $this->abz++; if ($this->abz >= $abzeile and $abzeile >= 0) { if ($abzeile == 0 and $this->abz == 1) { rewind($handle); } while (($data = fgetcsv($handle, $this->leng, $this->sepa)) !== false) { if ($this->whi < $restli){ $this->daten[] = $data; } $this->whi++; } } } fclose($handle); if (count($this->daten) !== 0) { $this->daten = array_reverse($this->daten); } else { $this->daten = array(0 => array("Keine Daten verfügbar!")); } return $this->daten; } else { return $daten = array(0 => array("Fehler, die Datei ". $this->csv." konnte nicht geladen werden!")); } } } /* -- Nur eine Blätterfunktion (eigentlich eine Klasse mit einer Methode) ------------ */ class BlaetterFunktion { public $fpage; // Seite public $fergeb; // Ergebnisse public $fline; // Zeilen public function getBlaetterSeite() { $blatt = ""; if ($this->fpage > 1) { $blatt .= "<a href=\"".htmlspecialchars("?". "seite=" .($this->fpage -1)."&". "anzahl=". $this->fergeb). "\">« zurück </a>"; } $blatt .= " | "; if (($this->fline /($this->fpage *$this->fergeb)) >= 1) { $blatt .= "<a href=\"".htmlspecialchars("?". "seite=" .($this->fpage +1)."&". "anzahl=". $this->fergeb). "\">weiter »</a>\n"; } return $blatt; } } $csvobj = new CsvInputOutput(); // Erzeugen und Instanziieren des Objektes CsvInputOutput $csvobj->setSeite(); // Rückgabewert der Methode setSeite $seite zuweisen $csvobj->setAnzahl(); // Rückgabewert der Methode setAnzahl $ergb zuweisen $csvobj->csv = $csvdat; // Die angegebene Datei $csv zuweisen $csvarr = $csvobj->getCSV(); // Der zurückgegebene Array $couarr = count($csvarr); // Ergebnisse für die erste for-Schleife zählen $numarr = count($csvarr[0]); // Felder der CSV für die zweite for-Schleife zählen $ergebn = $csvobj->ergb; // Anzahl der Ergebnisse für die erste for-Schleife $blatt = new BlaetterFunktion(); // Nur zum Weiterblättern $blatt->fpage = $csvobj->seite; // Seite 1, 2, 3 $blatt->fergeb = $csvobj->ergb; // Ergebnisse pro Seite zum Beispiel 20 oder 30 $blatt->fline = $csvobj->rows; // Zeilen zum Beispiel 200.000 ?> <!DOCTYPE html> <html> <head> <title>CSV-Dateien rückwärts auslesen</title> </head> <body> <h1 style="text-align:center">CSV-Dateien rückwärts lesen</h1> <?php /* -- Die Ausgabe kann nach Belieben dem Verwendungszweck angepasst werden ----------- */ echo "<ol>\n"; for ($i = 0; $i < $couarr; $i++) { echo "<li>\n<ul>\n"; for ($n = 0; $n < $numarr; $n++) { echo "<li>".htmlspecialchars($csvarr[$i][$n])."</li>"; } echo "</ul>\n</li>\n"; } echo "</ol>\n<p style=\"text-align:center\">".$blatt->getBlaetterSeite()."</p>\n"; ?> </body> </html>
Ergänzend noch einige Bemerkungen zur Laufzeit des Scripts und zur Ladezeit der Seiten. Bei den durchgeführten Tests wurden sowohl die Laufzeit des Scripts mit microtime, als auch die Ladezeit der Seiten beim Blättern mit Firebug getestet. Die angegeben Werte sind nur Durchschnittswerte, die sich von Seite zu Seite geringfügig unterscheiden.
Ladezeit der Seiten unter Localhost
        Blättern von einer Seite zur anderen
        
        Größe der CSV-Datei 181 MB mit 200.000 Zeilen
        Bei 20 auszugebenen Ergebnissen pro Seite: 561ms (onload: 627ms)
        
        Größe der CSV 16,2 MB mit 18.000 Zeilen
        Bei 20 auszugebenen Ergebnissen pro Seite: 110ms (onload: 166ms)
        Bei 50 auszugebenen Ergebnissen pro Seite: 140ms (onload: 207ms)
        
        Ladezeit der Seiten im Web
        Blättern von einer Seite zur anderen
        
        Größe der CSV 16,2 MB mit 18.000 Zeilen
        Bei 20 auszugebenen Ergebnissen pro Seite: 561ms (onload: 630ms)
Abschließend sei bemerkt, dass vorgestellte Script hält keinen Vergleich mit einer Datenbank stand und wurde nur für Testzwecke gefertigt. Wenn immer die Möglichkeit besteht, ist hingegen der gut beraten, der Daten aus CSV-Dateien in eine MySQL-Datenbank abspeichert und je nach Verwendungszweck, regelmäßig updatet.
Einstieg in PHP
 
 
Übersicht
Diverse Themen