Ü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