Domanda

Io ho a che fare con grandi file di testo (oltre 10 gigabyte, sì lo so che dipende da cosa dovremmo chiamare grandi dimensioni), con linee molto lunghe.

Il mio più recente operazione comporta qualche modifica linea basata su dati provenienti da un altro file.

Il file di dati (che dovrebbe essere modificato) contiene 1500000 righe, ciascuna di esse sono ad esempio 800 caratteri lunghi. Ogni linea è unica, e contiene un solo numero di identità, ogni numero di identità è unica)

Il file modificatore è per esempio lungo 1800 linee, contiene un numero di identificazione, e una quantità e una data che dovrebbe essere modificato nel file di dati.

Ho appena trasformato (con Vim regex) il file modificatore sed, ma è molto inefficiente.

Diciamo che ho una linea come questa nel file di dati:

(some 500 character)id_number(some 300 character)

E ho bisogno di modificare i dati nella parte 300 char.

In base al file modificatore, vengo con linee sed come questo:

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

Così ho 1800 linee in questo modo.

Ma io so, che anche su un server molto veloce, se faccio un

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

E 'molto lento, perché deve leggere ogni modello x ogni linea.

Non c'è un modo migliore?

Nota: Io non sono un programmatore, non avevo mai imparato (a scuola) su algoritmi. Posso usare awk, sed, una versione non aggiornata di Perl sul server.

È stato utile?

Soluzione

I miei approcci suggeriti (in ordine di preferibilmente) sarebbe quello di trattare tali dati come:

  1. Un database (anche un semplice DB SQLite-based con un indice si esibirà molto meglio di sed / awk su un file da 10 GB)
  2. Un file flat che contiene record di lunghezza fissa
  3. Un file flat che contiene lunghezze di registrazione variabile

Utilizzando un database si prende cura di tutti quei piccoli dettagli che rallentano l'elaborazione file di testo (trovare il record che ti interessano, modificare i dati, la memorizzazione di nuovo al DB). Date un'occhiata per DBD :: SQLite nel caso di Perl.

Se si vuole attaccare con file flat, ti consigliamo di mantenere un indice manualmente a fianco del grande file in modo da poter più facilmente cercare i numeri record di cui ha bisogno per manipolare. O, meglio ancora, forse i vostri numeri di ID sono i tuoi numeri da record?

Se si dispone di lunghezze di registrazione variabili, suggerirei la conversione in-registrare fisse lunghezze (dal momento che appare solo il tuo ID è di lunghezza variabile). Se non è possibile farlo, forse, tutti i dati esistenti non mai spostarsi nel file? Poi si può sostenere che in precedenza menzionato indice e aggiungere nuove voci, se necessario, con la differenza è che invece l'indice che punta a registrare il numero, ora punta alla posizione assoluta nel file.

Altri suggerimenti

Vi suggerisco un programma scritto in Perl (come io non sono un guru sed awk / e non so che cosa sono esattamente in grado di).

"algoritmo" è semplice: è necessario costruire prima di tutto, un hashmap che potrebbe dare la nuova stringa di dati da applicare per ogni ID. Ciò si ottiene la lettura del file modificatore naturalmente.

Una volta che questo hasmap in popolato si può navigare ogni riga del file di dati, leggere l'ID nel mezzo della linea, e generare la nuova linea, come hai descritto sopra.

Io non sono un guru di Perl troppo, ma penso che il programma è abbastanza semplice. Se hai bisogno di aiuto per scrivere, chiedere che: -)

Con perl si dovrebbe usare substr per ottenere id_number, soprattutto se id_number ha larghezza costante.

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

Dopo che se $ id_number è in campo, è necessario utilizzare substr per sostituire il testo rimanente.

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

espressioni regolari del Perl sono molto veloci, ma non in questo caso.

Il mio suggerimento è, non utilizzare database. script perl Ben scritto sorpasserà database in ordine di grandezza in questo tipo di operazione. Fidati di me, ho molti l'esperienza pratica con esso. Non sarà stato importato i dati nel database quando perl saranno finiti.

Quando si scrive 1500000 linee con 800 caratteri sembra 1.2GB per me. Se si avrà molto lento del disco (30 MB / s) che leggerete in un 40 secondi. Con una migliore 50 -> 24s, 100 -> 12s e così. Ma perl hash di ricerca (come db join) di velocità sulle 2GHz CPU è al di sopra 5Mlookups / s. Ciò significa che la CPU lavoro legato sarà in pochi secondi e si IO-lavoro rilegato sarà in decine di secondi. Se è davvero numeri 10GB cambieranno ma proporzione è lo stesso.

Non è stato specificato se la modifica dei dati cambia dimensione o no (se modifica può essere fatta sul posto) quindi non ci assumiamo e lavoreremo come filtro. Non è stato specificato il formato del file "modificatore" e che tipo di modifica. Si supponga che è separata da scheda qualcosa come:

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

Saremo leggere i dati da standard input e scrive su stdout e script può essere qualcosa di simile:

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 }

Il computer portatile miniera ci vogliono circa mezzo minuto per 1,5 milioni di righe, 1800 ids di ricerca, dati 1.2GB. Per 10GB non dovrebbe essere più di 5 minuti. E 'rapido ragionevole per voi?

Se si inizia a pensare che non sono IO vincolati (per esempio se utilizzare alcuni NAS), ma CPU legato si può sacrificare un po 'la leggibilità e cambiare a questo:

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

Si dovrebbe quasi certamente utilizzare un database, come MikeyB suggerì .

Se non si desidera utilizzare un database per qualche ragione, poi, se l'elenco delle modifiche si inserisce in memoria (come è attualmente sarà a 1800 linee), il metodo più efficace è una tabella hash popolato con le modifiche come suggerito da Yves Baumes .

Se si arriva al punto in cui anche l'elenco delle modifiche diventa enorme, è necessario risolvere entrambi i file dal loro ID e quindi eseguire un Elenco merge - in pratica:

  1. Confrontare l'ID al "top" del file di input con l'ID al "top" delle modifiche file
  2. Regolare il record di conseguenza se corrispondono
  3. Scrivi fuori
  4. Eliminare la linea "top" da qualsiasi file di ebbe la (in ordine alfabetico o numerico) ID basso e leggere un'altra linea da quel file
  5. Vai a 1.

Dietro le quinte, una banca dati sarà quasi certamente utilizzare un elenco unire se si esegue questa alterazione utilizzando un unico UPDATE comando SQL.

Buon affare sul sqlloader o DataDump decisione. Questo è il modo di andare.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top