将压缩的csv拆分为块的最有效方法
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将压缩的csv拆分为块的最有效方法相关的知识,希望对你有一定的参考价值。
我有几个非常大的,gzip压缩的csv文件(mysqldump
的压缩输出) - 每个大约65 GB。
我需要将它们分成压缩块,每个压缩块小于4 GB(压缩后),请记住每个csv文件中都存在引用的新行字符。
在'nix命令行上(例如在Debian中)执行此操作的最有效方法是什么?
与此SO类似,尽管回复未正确解释引用的换行符。
不使用临时磁盘空间的方法。
块大小估计
首先,由于每行的压缩比根据其内容而变化,因此除非运行2遍编码,否则很难获得精确的压缩后大小目标。但是,通过使用,您可以粗略估计压缩前块的大小
gzip -l file.csv.gz | tail -1 | awk '{print int(4*$2/$1)}'
此命令中的4
(GB)是压缩后每个块的目标大小,因此如果结果显示21
,则表示您可以使用大小为21
(GB)的块大小拆分未压缩文件,假设该文件具有均匀的熵分布。
解压缩,拆分和压缩
我们可以使用上面获得的块大小来分割文件
gzip -dc file.csv.gz | split -C 21G -d - split_ --filter='gzip > $FILE.csv.gz'
gzip -dc
将文件解压缩为stdoutsplit -C 21G
为每个输出文件提供最多21G的记录split --filter='gzip > $FILE.csv.gz'
为每个拆分文件启用直接压缩
额外提示:用gzip
替换上面的所有pigz
,以实现更快的多线程压缩
更新
要保留每个拆分的gzip压缩文件的标头,我们可以使用
gzip -dc file.csv.gz | tail -n+2 | split - --filter='{ gzip -dc file.csv.gz | head -1; cat; } | gzip > $FILE.csv.gz'
为了简单起见,这里忽略了split
的一些选项,但你明白了。诀窍是修改split
中的过滤器选项,以便它将原始csv文件的头部预先添加到每个分割文件的输出流中。
1 The gzip stream
由于gzip是具有历史表的压缩,因此您不能简单地拆分压缩流。你需要解码它。将输出传递给拆分器程序时,可能会节省一些磁盘空间。
2 The chunk size
接下来的任务是获得4GB的块。如果您无法安全地估算压缩比,唯一安全的方法是削减每4GB原始CSV数据。这可能会导致比4GB小得多的块。
3 The CSV stream
由于CSV格式(带换行符)不允许重新同步,因此也必须解析整个CVS流 - 假设您不是简单地想要将CSV拆分到任意位置但是在记录的末尾,即逻辑CVS线。
事实上,后一种程序可以稍微简化一下。假设CSV数据中的潜在双引号像往常一样使用双引号进行转义,则可以假定在偶数个双引号之后出现的每个换行符都是记录分隔符。因此,解析器任务减少到计算流中的双引号。
我很确定这仍然无法通过shell脚本合理地解决。我会推荐一些Perl脚本或类似的东西来进行解析和拆分。
脚本需要从stdin读取,计算字节数和双引号数,并将结果传递给gzip > targetfile
。每次字节数达到任务2的限制时,它应该在当前缓冲区中寻找流中偶数个双引号之后的换行符。然后将到此时为止的字节发送到当前的gzip实例,并关闭输出流。现在递增目标文件名并打开一个新的gzip输出,重置字节计数器并将当前缓冲区的剩余部分传递给新的gzip输出流。
以下脚本演示了解决方案:
#!/usr/bin/perl
use strict;
my $targetfile = "target";
my $limit = 1 << 32; # 4GB
my $filenum = 0;
open F, "|-", "gzip >$targetfile-$filenum.gz" or die;
my ($buffer, $bytes, $quotes);
while (read STDIN, $buffer, 1024*1024)
{ $bytes += length $buffer;
if ($bytes > $limit)
{ my $pos;
do
{ $pos = 1 + index $buffer, "
", $pos;
$pos or die "no valid delimiter found: $bytes";
} while (((substr($buffer, 0, $pos) =~ tr/"//) + $quotes) & 1);
print F substr $buffer, 0, $pos or die;
close F;
++$filenum;
open F, "|-", "gzip >$targetfile-$filenum.gz" or die;
$buffer = substr $buffer, $pos;
$bytes = length $buffer;
}
$quotes += $buffer =~ tr/"//;
print F $buffer or die;
}
close F;
潦草假设在1MB的块中至少有一个有效的记录分隔符。
4 Invoke the whole pipeline
gzip -d -c sourcefile | perlscript
这将完成整个任务。它不会使用超过几MB的内存,主要用于Perl解释器。
在磁盘上,您需要两倍的存储空间来保存源文件以及目标文件。
以上是关于将压缩的csv拆分为块的最有效方法的主要内容,如果未能解决你的问题,请参考以下文章