PHP: Wie kann man eine Textdatei, die sehr lange Zeilen enthält, effizient parsen?

8

Ich arbeite an einem Parser in PHP, der MySQL-Datensätze aus einer Textdatei extrahieren soll. Eine bestimmte Zeile kann mit einer Zeichenfolge beginnen, die der Tabelle entspricht, in die die Datensätze (Zeilen) eingefügt werden sollen, gefolgt von den Datensätzen selbst. Die Datensätze sind durch einen umgekehrten Schrägstrich getrennt und die Felder (Spalten) sind durch Kommata getrennt. Nehmen wir zur Vereinfachung an, dass wir eine Tabelle haben, die Personen in unserer Datenbank darstellt, mit den Feldern Vorname, Nachname und Beruf. Daher könnte eine Zeile der Datei wie folgt aussehen

[Leute]="\ Han, Solo, Schmuggler \ Luke, Skywalker, Jedi ..."

Wo die Ellipsen (...) zusätzliche Personen sein könnten. Ein einfacher Ansatz könnte darin bestehen, mit fgets() eine Zeile aus der Datei zu extrahieren und mit preg_match() den Tabellennamen, Datensätze und Felder aus dieser Zeile zu extrahieren.

Nehmen wir jedoch an, wir haben eine Menge Star Wars-Charaktere zu verfolgen. So viele in der Tat, dass diese Zeile endet mit mehr als 200.000 Zeichen / Bytes. In einem solchen Fall scheint die obige Vorgehensweise zum Extrahieren der Datenbankinformation ein wenig ineffizient zu sein. Sie müssen zuerst Hunderttausende von Zeichen in den Speicher lesen und dann zurück über dieselben Zeichen lesen, um passende Ausdrücke zu finden.

Gibt es einen Weg, ähnlich der Methode Java String next(String pattern) der Scanner Klasse, die mithilfe einer Datei erstellt wurde, mit der Sie Muster beim Scannen durch die Datei inline zuordnen können?

Die Idee ist, dass Sie nicht zweimal den gleichen Text scannen müssen (um ihn aus der Datei in eine Zeichenkette einzulesen und dann Muster zu vergleichen) oder den Text redundant im Speicher ablegen (in der Dateizeile) und die übereinstimmenden Muster). Würde dies sogar zu einer signifikanten Leistungssteigerung führen? Es ist schwer zu sagen, was PHP oder Java hinter den Kulissen tun.

Am fgetcsv()
Diese Funktion macht es sehr einfach, Zeilen in einer Datei basierend auf einem Trennzeichen zu teilen, und ich bin mir sicher, dass sie das Trennzeichen Zeichen für Zeichen sucht, während es die Datei durchsucht. Das Problem ist jedoch, dass es im Wesentlichen zwei Trennzeichen gibt, nach denen ich suche, und fgetcsv() akzeptiert nur eins. Zum Beispiel:

Ich könnte ',' als Trennzeichen verwenden. Vorausgesetzt, ich habe das Dateiformat so geändert, dass es auch Kommas mit einem umgekehrten Schrägstrich enthält, könnte ich die gesamte Zeile in ein Array von Feldern lesen. Das Problem ist dann, muss ich wiederholen über alle Felder zu bestimmen, wo Datensätze starten und beenden und die sql vorzubereiten. Wenn ich '\' als Begrenzer verwende (ein einzelner umgekehrter Schrägstrich, hier maskiert), dann muss ich wiederholen über alle Datensätze, um die Felder zu extrahieren und die sql vorzubereiten.

Was ich versuche, ist, auf beide Kommas und Backslashes (und vielleicht andere Dinge, wie den [tablename]) auf einen Schlag nach maximaler Leistung zu suchen. Wenn fgetcsv() mir erlaubt, mehrere Begrenzer (oder eine Regex) zu spezifizieren oder mir erlaubt, das zu ändern, was es als "Ende einer Zeile" ansieht (von \ n oder \ n \ r nach nur \), dann würde es funktionieren perfekt, aber das scheint nicht möglich.

    
Shaun 31.03.2010, 23:26
quelle

2 Antworten

3

Sie könnten eine zeichenweise Akkumulationsschleife schreiben, die (a) Feldstrings auf ein Array schiebt, wenn sie Kommas trifft und (b) eine Funktion aufruft, um akkumulierte Feldstrings in einer mysql-Datenbank zu speichern, wenn sie den Datensatz-Signifikanten findet :

%Vor%

Dies wird wahrscheinlich für Sie funktionieren, wenn Sie sicher sind, dass Ihre Felder niemals Ihr Feld oder Trennzeichen als Daten enthalten.

Wenn das eine Möglichkeit ist, müssen Sie eine Escape-Sequenz erstellen, um Literalwerte Ihres Feld- und Datensatztrennzeichens (und wahrscheinlich auch Ihrer Escape-Sequenz) darzustellen. Nehmen wir an, dass dies der Fall ist und nehmen Sie das% -Zeichen als Escape-Zeichen an:

%Vor%

dh, jedes Auftreten von% setzt eine Zustandsvariable, die beim nächsten Durchgang durch die Schleife anzeigt, welches Zeichen wir auch lesen, wird als Literaldaten genommen, die Teil eines Feldes und nicht eines Signifikators sind.

Dies sollte Ihre Speichernutzung auf einem Minimum halten.

[Update] Was ist mit der I / O-Effizienz?

Ein Kommentator wies zu Recht darauf hin, dass diese Illustration ziemlich I / O-intensiv ist, und da I / O dazu neigt, die teuerste Operation in Bezug auf die Zeit zu sein, ist es durchaus möglich, dass es keine akzeptable Lösung wäre.

>

An einem anderen Ende des Spektrums haben wir die Möglichkeit, die gesamte Datei in den Speicher zu puffern, was die ursprünglichen speicherintensiven Lösungen beinhaltet, die der Fragesteller erwähnte, aber vermeiden wollten. Das glückliche Medium liegt wahrscheinlich irgendwo in der Mitte: Wir können das Lesegrenze verwenden, das Sie als zweites Argument an fgets() übergeben können, um eine etwas große (aber nicht lächerlich große) Anzahl von Zeichen in einem einzigen I / O-Schluck zu ziehen , und verarbeiten Sie dann diesen Puffer Zeichen für Zeichen statt des E / A-Datenstroms und füllen Sie es neu, wenn wir den Puffer durchbrennen.

Dies macht den Leseprozess jedoch etwas mehr Code-intensiv als $c = fgetc($fp) , weil Sie überwachen müssen, wo Sie sich im Puffer befinden und wie voll der Puffer ist und wo Sie sich in der Datei befinden. Sie können dies mit einer Reihe von Flags und Indexvariablen innerhalb der Leseschleife tun, wenn Sie möchten, aber es könnte zweckmäßiger sein, eine Abstraktion in etwa so zu haben:

%Vor%

Was Sie in beiden obigen Schleifen verwenden könnten:

%Vor%

Auf diese Weise können Sie viele verschiedene Punkte entlang des Kontinuums zwischen einer speicherintensiven Lösung und einer E / A-intensiven Lösung ablegen, indem Sie $ bufferSize ändern. Größere $ pufferSize, mehr Speicherauslastung, weniger E / A-Operationen. Kleinere $ pufferSize, weniger Speicherverbrauch, mehr E / A-Operationen.

(Hinweis: gehen Sie nicht davon aus, dass die Klasse produktionsbereit ist. Sie ist als Illustration für eine mögliche Abstraktion gedacht, kann einzelne oder andere Fehler enthalten. Kann verschwommenes Sehen, Schlafmangel, Herzklopfen, oder andere Nebenwirkungen.Überprüfen Sie vor der Verwendung mit einem Arzt und Gerätetests.)

    
Weston C 01.04.2010, 06:15
quelle
0

Vielleicht verwenden Sie die Funktion strtok ()?

$ string="Hallo Welt. Schöner Tag heute."; $ token = strtok ($ string, "");

while ($ token! = false)   {   echo "$ token
";   $ token = strtok ("");   }

    
SethCoder 01.04.2010 13:00
quelle

Tags und Links