[Cologne-pm] große Files parsen
A. Pagaltzis
pagaltzis at gmx.de
Mon Jan 16 19:26:46 PST 2006
Hi Johannes,
* Johannes Huettemeister <j.huettemeister at googlemail.com> [2006-01-16 20:55]:
>ich hab hier ein Codeschnipsel fabriziert, das funktioniert,
>aber mit Sicherheit optimiert werden kann.
An Optimierung würde ich erst garnicht denken, jedenfalls nicht
im Sinne von Geschwindigkeit; der Code ist in der vorliegenden
Form völlig unlesbar. Ich habe gut 5 Minuten draufstarren müssen,
bis mir klar war, wie die Bauteile alle zusammenpassen.
>sysopen (FILE, $filename, O_RDONLY) or die "$!";
>seek(FILE,$offset,$startpos);
>
>my $b = ''; my @f; $togo = 0;
>for ($curpos = tell(FILE); my $c = read(FILE, $b, 1024, length($b)) ;
>$curpos = tell(FILE)) {
> @f = split(/^/m, $b);
> foreach (@f) {
> unless (m,\n,) {
> $b = $_;
> last;
> }
> do_something_with_line
> }
> last if ($togo++ == 5000);.
>}
Vor allem ist der Code fehlerhaft:
1. Du setzt `$b` nur dann zurück, wenn eine Zeile kein `\n`
enthält. Wenn du also einen 1024er-Happen Daten einliest, der
zufällig genau am Ende einer Zeile endet, dann wird `$b`
nirgendwo geleert, und der nächste Durchlauf der Schleife
wird die Daten dann doppelt verarbeiten.
Das ist es, was mir das Verständnis so erschwert hat: es
schien, als wolltest du Daten früherer Durchläufe mehrfach
verarbeiten, was offensichtlich ja nicht die Absicht ist.
2. Wenn die letzte Zeile der Datei nicht auf ein `\n` endet,
schmeisst du sie weg.
3. Wenn der letzte der 5000 der 1024er-Happen nicht genau auf
ein Zeilenende endet, wirfst du Daten weg, weil die innere
Schleife dann zwar diesen Rest nach `$b` zuweist, die äussere
aber kein weiteres Mal mehr durchläuft. Und du spulst den
Dateicursor auch nicht zurück, um diese Daten beim nächsten
Lauf mitzunehmen.
Ausserdem ist es merkwürdig, dass du so obsessiv bei jedem
Schleifendurchlauf `$curpos` speicherst. Wenn der hier gezeigte
Code nicht zu unvollständig ist, reicht es, das *ein* einziges
Mal *nach* der Schleife zu machen.
Warum du hier `sysopen` verwendest, ist unklar; und du solltest
lieber mit lexikalischen Filehandles arbeiten statt Barewords.
Zuguterletzt: statt selber `$togo` zu verwalten, würde ich eher
eine `foreach`-Schleife von 1-5000 nehmen, aus der ich bei Bedarf
nach dem Lesen per `last` aussteige.
Summa summarum:
open my $fh, '<', $filename
or die "$!";
seek $fh, $offset, $startpos;
# on each iteration, $buf may contain the start of
# an incomplete line from the end of the previous
# iteration's chunk of data
my $buf = '';
my $chunk_len = 1024;
for( 1 .. 5000 ) {
my $read_len = read( $fh, $buf, $chunk_len, length $buf )
or last;
my @line = split /^/m, $buf;
$buf = ( $line[ -1 ] =~ /\n\z/ ) || ( $read_len != $chunk_len )
? ''
: pop @line;
foreach ( @line ) {
do_something_with_line();
}
}
$curpos = tell $fh;
# account for unprocessed data
$curpos -= do {
use bytes;
length $buf;
};
Gruss,
--
#Aristoteles
*AUTOLOAD=*_;sub _{s/(.*)::(.*)/print$2,(",$\/"," ")[defined wantarray]/e;$1};
&Just->another->Perl->hacker;
More information about the Cologne-pm
mailing list