在 Perl 或 Python 中按列值将 100 亿行文件拆分为 5,000 个文件

split 10 billion line file into 5,000 files by column value in Perl or Python

我有一个 100 亿行制表符分隔的文件,我想根据一列(第一列)拆分为 5,000 个子文件。我怎样才能在 Perl 或 Python 中有效地做到这一点?

之前已经在这里问过这个问题,但是所有的方法都会为读取的每一行打开一个文件,或者它们将所有数据都放在内存中。

awk 救援!

awk 'f!={close(f)} {f=; print >> f}' file

它将逐行处理,一次打开一个文件。

如果将原始文件拆分成块,这可以更有效地并行完成并合并生成的文件(如果需要保留顺序,则需要标记它们)

您可以保留哈希(关联数组)映射列值以打开输出文件句柄,并且仅当 none 已为该列值打开时才打开输出文件。

除非您达到打开文件的最大数量限制,否则这已经足够了。 (使用 ulimit -Hnbash 中看到它。)如果你这样做,要么你需要关闭文件句柄(例如,一个随机的,或者最长时间没有被使用的那个,这很容易以在另一个哈希中跟踪),或者您需要对输入进行多次传递,只处理一次传递中可以打开输出文件的尽可能多的列值,并在以后的传递中跳过它们。

这个程序会按照你的要求去做。它期望输入文件作为命令行上的参数,并写入名称取自输入文件记录第一列的输出文件

它保留文件句柄的散列 %fh 和标志的并行散列 %opened,这些标志指示给定文件之前是否曾被打开过。如果文件出现在 %opened 散列中,则打开该文件进行追加,如果以前从未打开过,则打开该文件进行写入。如果达到打开文件的限制,则关闭(随机)选择的 1,000 个文件句柄。跟踪每个句柄上次使用的时间并关闭最过时的句柄是没有意义的:如果输入文件中的数据是随机排序的,那么散列中的每个句柄都有相同的机会成为下一个被使用的对象,或者,如果数据已经排序,则 none 的文件句柄将再次使用

use strict;
use warnings 'all';

my %fh;
my %opened;

while ( <> ) {

    my ($tag) = split;

    if ( not exists $fh{$tag} ) {

        my $mode = $opened{$tag} ? '>>' : '>';

        while () {

            eval {
                open $fh{$tag}, $mode, $tag or die qq{Unable to open "$tag" for output: $!};
            };

            if ( not $@ ) {
                $opened{$tag} = 1;
                last;
            }

            die $@ unless $@ =~ /Too many open files/;

            my $n;
            for my $tag ( keys %fh ) {
                my $fh = delete $fh{$tag};
                close $fh or die $!;
                last if ++$n >= 1_000 or keys %fh == 0;
            }
        }
    }

    print { $fh{$tag} } $_;
}


close $_ or die $! for values %fh;