8

非常に大きなプレーン テキスト ファイル (10 ギガバイト以上、何を大容量と呼ぶべきかによる) を非常に長い行で処理する必要があります。

私の最近のタスクには、別のファイルのデータに基づいた行の編集が含まれます。

データ ファイル (変更する必要があります) には 1500000 行が含まれており、各行の長さはたとえば 800 文字です。各行は一意であり、1 つの ID 番号のみが含まれます。各 ID 番号は一意です)。

モディファイヤ ファイルは、たとえば 1800 行の長さで、ID 番号と、データ ファイルで変更する必要がある金額と日付が含まれています。

モディファイヤ ファイルを (Vim regex で) sed に変換しましたが、非常に非効率的です。

データ ファイルに次のような行があるとします。

(some 500 character)id_number(some 300 character)

そして、300文字の部分のデータを変更する必要があります。

モディファイヤ ファイルに基づいて、次のような sed 行を作成します。

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

だから私はこのような1800行を持っています。

しかし、非常に高速なサーバーでも、

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

すべてのパターン x すべての行を読み取る必要があるため、非常に低速です。

より良い方法はありませんか?

注:私はプログラマーではなく、(学校で) アルゴリズムについて学んだことがありません。サーバー上で awk、sed、古いバージョンの perl を使用できます。

4

6 に答える 6

6

私が提案するアプローチ (望ましい順に) は、このデータを次のように処理することです。

  1. データベース (インデックスを持つ単純な SQLite ベースの DB でさえ、10 GB のファイルで sed/awk よりもはるかに優れたパフォーマンスを発揮します)
  2. 固定レコード長を含むフラット ファイル
  3. 可変レコード長を含むフラット ファイル

データベースを使用することで、テキスト ファイルの処理速度を低下させるすべての細かな処理 (重要なレコードの検索、データの変更、DB への保存) に対処できます。Perl の場合は DBD::SQLite を調べてください。

フラット ファイルに固執する場合は、操作が必要なレコード番号をより簡単に検索できるように、大きなファイルと一緒に手動でインデックスを維持する必要があります。それとも、あなたの ID 番号あなたの記録番号ではないでしょうか?

レコード長が可変の場合は、固定レコード長に変換することをお勧めします (ID のみが可変長であるように見えるため)。それができない場合、おそらく既存のデータはファイル内で移動しませんか? 次に、前述のインデックスを維持し、必要に応じて新しいエントリを追加できます。違いは、インデックスがレコード番号を指すのではなく、ファイル内の絶対位置を指すことです。

于 2009-05-11T17:17:41.390 に答える
2

perl では、特に id_number の幅が一定の場合、substr を使用して id_number を取得する必要があります。

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

その後、$id_number が範囲内にある場合は、substr を使用して残りのテキストを置き換える必要があります。

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

Perl の正規表現は非常に高速ですが、この場合はそうではありません。

于 2009-05-11T17:18:58.547 に答える
1

私の提案は、データベースを使用しないことです。適切に作成された perl スクリプトは、この種のタスクでデータベースよりも優れたパフォーマンスを発揮します。私を信じてください、私はそれについて多くの実践的な経験を持っています. perl が終了すると、データベースにデータがインポートされません。

800文字で1500000行を書くと、私には1.2GBのようです。非常に遅いディスク (30MB/秒) を使用する場合は、40 秒で読み取ります。より良い 50 -> 24 秒、100 -> 12 秒など。しかし、2GHz CPU での perl ハッシュ ルックアップ (db join など) の速度は 5Mlookups/s を超えています。これは、CPU バウンドの作業が数秒で、IO バウンドの作業が数十秒になることを意味します。本当に 10GB の場合、数字は変わりますが、割合は同じです。

データの変更がサイズを変更するかどうか (変更をその場で行うことができる場合) を指定していないため、それを想定せず、フィルターとして機能します。「修飾子ファイル」の形式と変更の種類を指定していません。次のようなタブで区切られていると仮定します。

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

stdin からデータを読み取り、stdout に書き込みます。スクリプトは次のようになります。

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 }

私のラップトップでは、150 万行、1800 のルックアップ ID、1.2GB のデータに約 30 分かかります。10GB の場合、5 分を超えてはなりません。あなたにとって合理的な速さですか?

IO バウンドではなく (たとえば、NAS を使用している場合)、CPU バウンドであると考え始める場合は、読みやすさを犠牲にして、次のように変更できます。

my $mod;
while (<>) {
  next unless m/$id_regexp/;
  $mod = $modifications{$1};
  next unless $mod;
  substr $_, $+[1] + $mod->[0], $mod->[1], $mod->[2];
}
continue { print }
于 2009-05-16T22:29:35.067 に答える
0

MikeyB が提案したように、ほぼ確実にデータベースを使用する必要があります。

何らかの理由でデータベースを使用したくない場合、変更のリストがメモリに収まる場合 (現在は 1800 行になります)、最も効率的な方法は、yves Baumesによって提案された変更が取り込まれたハッシュテーブルです。 .

変更のリストが膨大になる場合は、両方のファイルを ID で並べ替えてから、リストのマージを実行する必要があります。基本的には次のとおりです。

  1. 入力ファイルの「先頭」の ID と変更ファイルの「先頭」の ID を比較します。
  2. 一致する場合は、それに応じてレコードを調整します
  3. 書き出す
  4. (アルファベット順または数字順で) 最も小さい ID を持つファイルの「先頭」行を破棄し、そのファイルから別の行を読み取ります。
  5. 1へ。

舞台裏では、単一の SQLUPDATEコマンドを使用してこの変更を実行すると、データベースはほぼ確実にリスト マージを使用します。

于 2009-05-12T12:52:41.860 に答える
0

sqlloader または datadump の決定についてはお得です。それが行く方法です。

于 2009-05-12T19:04:46.027 に答える