Frage

Ich habe mit sehr großen Textdateien umgehen (über 10 Gigabyte, ja, ich weiß, es hängt, was wir groß nennen sollen), mit sehr langen Linien.

Meine letzte Aufgabe beinhaltet einige Zeilenbearbeitung auf Basis von Daten aus einer anderen Datei.

Die Datendatei (die modifiziert werden soll) enthält 1500000 Linien, jeweils von ihnen z.B. 800 Zeichen lang. Jede Zeile ist einzigartig und enthält nur eine Identitätsnummer, die jeweils Identitätsnummer ist einzigartig)

Der Modifikator-Datei ist beispiels 1800 Zeilen lang, enthält eine Identitätsnummer, und eine Menge und ein Datum, das in der Datendatei geändert werden soll.

Ich habe gerade umgewandelt (mit Vim regex) der Modifikator-Datei sed, aber es ist sehr ineffizient.

Lassen Sie uns sagen, dass ich eine Zeile wie diese in der Datendatei haben:

(some 500 character)id_number(some 300 character)

Und ich brauche Daten in dem 300 Zeichen Teil zu ändern.

Auf der Grundlage der Modifikator-Datei, komme ich mit sed Zeilen wie diese auf:

/id_number/ s/^\(.\{650\}\).\{20\}/\1CHANGED_AMOUNT_AND_DATA/

So habe ich 1800 Zeilen wie diese.

Aber ich weiß, dass auch auf einem sehr schnellen Server, wenn ich ein

sed -i.bak -f modifier.sed data.file

Es ist sehr langsam, weil es jedes Muster x jede Zeile zu lesen hat.

Gibt es nicht einen besseren Weg?

Hinweis: Ich bin kein Programmierer, hatte nie gelernt, (in der Schule) über Algorithmen. Ich kann awk verwenden, sed, eine veraltete Version von Perl auf dem Server.

War es hilfreich?

Lösung

Meine vorgeschlagenen Ansätze (in der Reihenfolge der wünschenswerterweise) wären, diese Daten zu verarbeiten, wie:

  1. Eine Datenbank (auch eine einfache SQLite-basierte DB mit einem Index wird durchführt viel besser als sed / awk auf einer 10GB-Datei)
  2. Eine flache Datei mit festen Satzlängen
  3. Eine flache Datei mit variabler Satzlängen

eine Datenbank verwenden kümmert sich um all die kleinen Details, die Text-Datei Verarbeitung verlangsamen (Suche nach der Aufzeichnung die Sie interessieren, um die Daten zu verändern, sie zurück in die DB zu speichern). Werfen Sie einen Blick für DBD :: SQLite im Fall von Perl.

Wenn Sie mit flachen Dateien bleiben wollen, sollten Sie einen Index manuell neben der großen Datei erhalten, so dass Sie leichter die Datensatznummern nachschlagen können Sie zu manipulieren benötigen, um. Oder, noch besser, vielleicht Ihre ID-Nummern sind Ihre Rekordzahlen?

Wenn Sie variable Satzlängen haben, würde ich auf Festsatzlängen vorschlagen Umwandlung (da es nur Ihre ID erscheint, ist mit variabler Länge). Wenn Sie das nicht tun können, vielleicht alle vorhandenen Daten werden nicht immer in der Datei bewegen? Dann können Sie behaupten, dass die zuvor erwähnten Index und neue Einträge wie nötig hinzufügen, mit dem Unterschied, dass statt der Indexnummer aufzuzeichnen zeigen, jetzt auf die absolute Position in der Datei verweisen.

Andere Tipps

Ich schlage vor, Sie ein Programm in Perl geschrieben (wie ich nicht ein sed / awk Guru bin und ich weiß nicht, was sie sind genau fähig ist).

Sie „Algorithmus“ ist einfach: Sie brauchen, vor allem, eine hashmap zu konstruieren, die Sie die neue Datenstring für jede ID anwenden geben könnte. Dies wird erreicht, die Modifikator-Datei natürlich zu lesen.

Sobald diese hasmap in bevölkerten Sie jede Zeile der Datendatei durchsuchen können, lesen Sie die ID in der Mitte der Strecke, und erzeugen die neue Linie, wie Sie oben beschrieben haben.

Ich bin kein Perl-Guru auch, aber ich denke, dass das Programm ganz einfach. Wenn Sie Hilfe benötigen, es zu schreiben, fragen Sie nach: -)

Mit Perl sollten Sie substr verwenden id_number zu bekommen, vor allem, wenn id_number konstante Breite hat.

my $id_number=substr($str, 500, id_number_length);

Danach, wenn $ id_number in Reichweite ist, sollten Sie substr verwenden, um restlichen Text zu ersetzen.

substr($str, -300,300, $new_text);

Perl reguläre Ausdrücke sind sehr schnell, aber nicht in diesem Fall.

Mein Vorschlag ist, verwenden Sie keine Datenbank. Gut geschriebene Perl-Skript Datenbank in Größenordnung in dieser Art von Aufgabe zu übertreffen. Vertrauen Sie mir, ich habe viele praktische Erfahrungen mit ihm. Sie haben keine Daten in die Datenbank importiert, wenn perl abgeschlossen sein wird.

Wenn Sie 1500000 Zeilen mit 800 Zeichen schreiben scheint es 1,2 GB für mich. Wenn Sie sehr langsam Scheibe (30MB / s) haben Sie es in einer 40 Sekunden abgelesen. Mit einem besseren 50 -> 24s, 100 -> 12s und so. Aber Perl Hash-Lookup (wie db join) Geschwindigkeit auf 2 GHz CPU ist über 5Mlookups / s. Es bedeutet, dass Ihre CPU gebunden Arbeit in Sekunden sein wird, und Sie IO gebunden Arbeit in zehn Sekunden sein wird. Wenn es wirklich 10GB Zahlen ändern aber Anteil ist gleich.

Sie haben nicht angegeben, wenn Datenänderung Größe ändert oder nicht (wenn Modifikation an Ort und Stelle durchgeführt werden kann) so werden wir es nicht annehmen, und als Filter arbeiten. Sie haben nicht angegeben, welches Format Ihrer „Modifikator-Datei“ und welche Art von Veränderung. Es sei angenommen, dass es durch Tab etwas wie getrennt ist:

<id><tab><position_after_id><tab><amount><tab><data>

Wir werden Daten von stdin gelesen und auf die Standardausgabe und Skript kann so etwas wie dieses schreiben:

my $modifier_filename = 'modifier_file.txt';

open my $mf, '<', $modifier_filename or die "Can't open '$modifier_filename': $!";
my %modifications;
while (<$mf>) {
   chomp;
   my ($id, $position, $amount, $data) = split /\t/;
   $modifications{$id} = [$position, $amount, $data];
}
close $mf;

# make matching regexp (use quotemeta to prevent regexp meaningful characters)
my $id_regexp = join '|', map quotemeta, keys %modifications;
$id_regexp = qr/($id_regexp)/;     # compile regexp

while (<>) {
  next unless m/$id_regexp/;
  next unless $modifications{$1};
  my ($position, $amount, $data) = @{$modifications{$1}};
  substr $_, $+[1] + $position, $amount, $data;
}
continue { print }

Auf Mine Laptop es für 1,5 Millionen Zeilen, 1800 Lookup-IDs, 1,2 GB Daten etwa eine halbe Minute dauert. Für 10GB sollte es nicht mehr als 5 Minuten. Ist es vernünftig, schnell für Sie?

Wenn Sie beginnen, denken Sie nicht IO gebunden sind (zum Beispiel, wenn einige NAS verwenden), aber die CPU gebunden Sie einige Lesbarkeit opfern kann und dies ändern:

my $mod;
while (<>) {
  next unless m/$id_regexp/;
  $mod = $modifications{$1};
  next unless $mod;
  substr $_, $+[1] + $mod->[0], $mod->[1], $mod->[2];
}
continue { print }

Sie sollten an Sicherheit grenzender Wahrscheinlichkeit eine Datenbank verwenden, wie MikeyB vorgeschlagen .

Wenn Sie nicht wollen, eine Datenbank aus irgendeinem Grunde verwenden, dann, wenn die Liste der Änderungen in dem Speicher passen wird (wie es derzeit bei 1800 Linien), die effizienteste Methode, eine Hash-Tabelle mit den Änderungen bevölkert ist, wie vorgeschlagen von yves Baum .

Wenn Sie an den Punkt, wo auch die Liste der Änderungen riesig wird, müssen Sie durch ihre IDs beiden Dateien sortieren und dann einen Liste Druck durchführen - im Grunde:

  1. Vergleichen Sie die ID an der „Spitze“ der Eingabedatei mit der ID an der „Spitze“ der Änderungen Datei
  2. Stellen Sie den Datensatz entsprechend, wenn sie übereinstimmen
  3. Schreiben Sie es heraus
  4. Entsorgen Sie die „top“ Zeile aus welcher Datei die (alphabetisch oder numerisch) tiefster ID hatte und eine weitere Zeile aus dieser Datei
  5. lesen
  6. Goto 1.

Hinter den Kulissen wird eine Datenbank mit ziemlicher Sicherheit eine Liste verwenden fusionieren, wenn Sie diese Änderung durchführen einen einzelnen SQL-UPDATE Befehl.

Gutes Geschäft auf dem sqlloader oder DataDump Entscheidung. Das ist der Weg zu gehen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top