计算大文件中的行数

我通常处理约20Gb 大小的文本文件,我发现自己经常计算给定文件中的行数。

我现在做的方式只是 cat fname | wc -l,它需要很长的时间。有没有更快的办法?

我在一个安装了 Hadoop 的高性能集群中工作。

我希望解决方案像 wc -l解决方案那样简单,只需运行一行代码,但不确定它的可行性。

有什么想法吗?

120907 次浏览

试试: sed -n '$=' filename

猫也是不必要的: wc -l filename在你现在的方式已经足够了。

限制速度的因素是存储设备的 I/O 速度,因此在简单换行/模式计数程序之间进行更改没有任何帮助,因为这些程序之间的执行速度差异可能会被较慢的磁盘/存储器/任何你拥有的东西所抑制。

但是,如果在磁盘/设备之间复制了相同的文件,或者文件分布在这些磁盘之间,那么当然可以并行执行操作。我不知道这个 Hadoop 的具体情况,但是假设你可以从4个不同的位置读取10gb 的文件,你可以运行4个不同的行计数过程,每个都在文件的一个部分,然后把它们的结果相加:

$ dd bs=4k count=655360 if=/path/to/copy/on/disk/1/file | wc -l &
$ dd bs=4k skip=655360 count=655360 if=/path/to/copy/on/disk/2/file | wc -l &
$ dd bs=4k skip=1310720 count=655360 if=/path/to/copy/on/disk/3/file | wc -l &
$ dd bs=4k skip=1966080 if=/path/to/copy/on/disk/4/file | wc -l &

注意每个命令行上的 &,因此所有命令都将并行运行; 在这里,dd的工作方式与 cat相似,但允许我们指定要读取多少字节(count * bs字节) ,以及在输入开始处要跳过多少字节(skip * bs字节)。它以块的形式工作,因此需要指定 bs作为块大小。在这个例子中,我已经将10Gb 文件分为4个相等的块,每个块的大小为4Kb * 655360 = 2684354560字节 = 2.5 GB,每个作业一个,您可能需要根据文件的大小和将要运行的并行作业的数量来设置一个脚本来完成这项工作。您还需要对执行的结果进行求和,这是由于缺乏 shell 脚本能力而没有做到的。

如果您的文件系统足够聪明,可以在多个设备之间分割大文件,比如 RAID 或分布式文件系统或其他设备,并自动并行化可以并行化的 I/O 请求,那么您可以进行这样的分割,运行许多并行作业,但使用相同的文件路径,您仍然可以获得一些速度增益。

编辑: 我想到的另一个想法是,如果文件内部的行大小相同,那么可以通过将文件大小除以行的大小(均以字节为单位)来得到确切的行数。你几乎可以在一个任务中立即完成。如果您有平均大小,并且不太关心行计数,但是想要一个估计值,那么可以执行相同的操作,并且比精确的操作更快地得到满意的结果。

Hadoop 本质上提供了一种机制来执行与@Ivella 所建议的类似的操作。

Hadoop 的 HDFS (分散式档案系统)会将你的20GB 文件以固定大小的块的形式保存在集群中。假设您将块大小配置为128MB,那么文件将被分割成20x8x128MB 的块。

然后对这些数据运行 map reduce 程序,实质上是计算每个块的行数(在 map stage 中) ,然后将这些块行数减少到整个文件的最终行数。

至于性能,一般来说,集群越大,性能就越好(在更多独立磁盘上并行运行更多 wc) ,但是作业编排存在一些开销,这意味着在更小的文件上运行作业实际上不会比运行本地 wc 产生更快的吞吐量

如果您的计算机有 python,您可以尝试从 shell:

python -c "print len(open('test.txt').read().split('\n'))"

这将使用 python -c传入一个命令,该命令基本上是读取文件,并通过“ newline”进行分割,以获取 newlines 的计数或文件的总长度。

@ BlueMoon’s@/a >

bash-3.2$ sed -n '$=' test.txt
519

使用以上方法:

bash-3.2$ python -c "print len(open('test.txt').read().split('\n'))"
519

如果您的数据驻留在 HDFS 上,也许最快的方法是使用 hadoop 流。Apache 猪的 COUNT UDF 操作在一个包上,因此使用一个简化器来计算行数。相反,你可以在一个简单的 hadoop 流脚本中手动设置减少器的数量,如下所示:

$HADOOP_HOME/bin/hadoop jar $HADOOP_HOME/hadoop-streaming.jar -Dmapred.reduce.tasks=100 -input <input_path> -output <output_path> -mapper /bin/cat -reducer "wc -l"

注意,我手动将 reducers 的数量设置为100,但是您可以调整这个参数。完成 map-reduce 任务之后,每个 reduce 的结果都存储在一个单独的文件中。行的最终计数是所有简化程序返回的数字之和。您可以得到行的最终计数如下:

$HADOOP_HOME/bin/hadoop fs -cat <output_path>/* | paste -sd+ | bc

我不确定蟒蛇是否更快:

[root@myserver scripts]# time python -c "print len(open('mybigfile.txt').read().split('\n'))"


644306




real    0m0.310s
user    0m0.176s
sys     0m0.132s


[root@myserver scripts]# time  cat mybigfile.txt  | wc -l


644305




real    0m0.048s
user    0m0.017s
sys     0m0.074s
find  -type f -name  "filepattern_2015_07_*.txt" -exec ls -1 {} \; | cat | awk '//{ print $0 , system("cat " $0 "|" "wc -l")}'

产出:

在多核服务器上,使用 GNU 并行并行计算文件行数。在打印每个文件的行数之后,bc 对所有行数求和。

find . -name '*.txt' | parallel 'wc -l {}' 2>/dev/null | paste -sd+ - | bc

为了节省空间,您甚至可以压缩所有文件。下面一行解压缩每个文件并并行计算其行数,然后对所有计数求和。

find . -name '*.xz' | parallel 'xzcat {} | wc -l' 2>/dev/null | paste -sd+ - | bc

根据我的测试,我可以验证 Spark-Shell (基于 Scala)比其他工具(GREP、 SED、 AWK、 PERL、 WC)快得多。下面是我在一个有23782409行的文件上运行的测试结果

time grep -c $ my_file.txt;

真正的0m44.96 用户0m41.59 s 系统0m3.09 s

time wc -l my_file.txt;

真正的0m37.57 用户0m33.48 s 系统0m3.97

time sed -n '$=' my_file.txt;

真正的0m38.22秒 用户0m28.05 s 系统0m10.14 s

time perl -ne 'END { $_=$.;if(!/^[0-9]+$/){$_=0;};print "$_" }' my_file.txt;

真正的0m23.38 用户0m20.19 s 系统0m3.11 s

time awk 'END { print NR }' my_file.txt;

真正的0m19.90秒 用户0m16.76 s 系统0m3.12 s

spark-shell
import org.joda.time._
val t_start = DateTime.now()
sc.textFile("file://my_file.txt").count()
val t_end = DateTime.now()
new Period(t_start, t_end).toStandardSeconds()

Res1: org.joda.time 秒数 = PT15S

如果您的瓶颈是磁盘,那么如何读取它就很重要了。dd if=filename bs=128M | wc -l是一个 很多wc -l filenamecat filename | wc -l更快,我的机器有一个硬盘和快速的 CPU 和 RAM。您可以调整块大小,查看 dd报告的吞吐量。我把频率调到了1GiB。

注意: 关于 catdd是否更快还存在一些争议。所有我声称的是,dd可以更快,这取决于系统,它是为我。你自己试试。

我知道这个问题已经有几年历史了,但是在 艾薇拉的最后一个主意上进行扩展,这个 bash 脚本 估计数字通过测量一行的大小并从中推断出一个大文件在几秒钟或更短时间内的行数:

#!/bin/bash
head -2 $1 | tail -1 > $1_oneline
filesize=$(du -b $1 | cut -f -1)
linesize=$(du -b $1_oneline | cut -f -1)
rm $1_oneline
echo $(expr $filesize / $linesize)

如果将此脚本命名为 lines.sh,则可以调用 lines.sh bigfile.txt以获得估计的行数。在我的例子中(大约6GB,从数据库导出) ,与真实行计数的偏差只有3% ,但是运行速度要快大约1000倍。顺便说一下,我使用第二行而不是第一行作为基础,因为第一行有列名,实际数据从第二行开始。

让我们假设:

  • 您的文件系统是分布式的
  • 您的文件系统可以轻松地填充到单个节点的网络连接
  • 你像普通文件一样访问你的文件

然后你真的想把文件分成几部分,在多个节点上并行地计算各部分的数量,并从中总结出结果(这基本上是@Chris White 的想法)。

下面是如何使用 GNU 并行(版本 > 20161222)实现这一点。您需要列出 ~/.parallel/my_cluster_hosts中的节点,并且必须让 ssh访问所有这些节点:

parwc() {
# Usage:
#   parwc -l file


# Give one chunck per host
chunks=$(cat ~/.parallel/my_cluster_hosts|wc -l)
# Build commands that take a chunk each and do 'wc' on that
# ("map")
parallel -j $chunks --block -1 --pipepart -a "$2" -vv --dryrun wc "$1" |
# For each command
#   log into a cluster host
#   cd to current working dir
#   execute the command
parallel -j0 --slf my_cluster_hosts --wd . |
# Sum up the number of lines
# ("reduce")
perl -ne '$sum += $_; END { print $sum,"\n" }'
}

用途:

parwc -l myfile
parwc -w myfile
parwc -c myfile

我有一个645GB 的文本文件,没有一个早期的精确解决方案(例如 wc -l)在5分钟内返回一个答案。

相反,这里是 Python 脚本,它计算一个巨大文件中的 大概行数。(我的文本文件显然有大约55亿行。)Python 脚本执行以下操作:

计算文件中的字节数。

读取文件中的第一个 N行(作为示例)并计算平均行长度。

计算 A/B 作为大致的行数。

它沿着 Nico 的回答的直线运行,但是不取一行的长度,而是计算第一个 N行的平均长度。

注意: 我假设一个 ASCII 文本文件,所以我希望 Python len()函数返回字符数作为字节数。

将此代码放入文件 line_length.py:

#!/usr/bin/env python


# Usage:
# python line_length.py <filename> <N>


import os
import sys
import numpy as np


if __name__ == '__main__':


file_name = sys.argv[1]
N = int(sys.argv[2]) # Number of first lines to use as sample.
file_length_in_bytes = os.path.getsize(file_name)
lengths = [] # Accumulate line lengths.
num_lines = 0


with open(file_name) as f:
for line in f:
num_lines += 1
if num_lines > N:
break
lengths.append(len(line))


arr = np.array(lengths)
lines_count = len(arr)
line_length_mean = np.mean(arr)
line_length_std = np.std(arr)


line_count_mean = file_length_in_bytes / line_length_mean


print('File has %d bytes.' % (file_length_in_bytes))
print('%.2f mean bytes per line (%.2f std)' % (line_length_mean, line_length_std))
print('Approximately %d lines' % (line_count_mean))

像这样用 N = 5000调用它。

% python line_length.py big_file.txt 5000


File has 645620992933 bytes.
116.34 mean bytes per line (42.11 std)
Approximately 5549547119 lines

所以文件中大约有55亿行。

随着较慢的 IO 回落到 dd if={file} bs=128M | wc -l极大地有助于收集数据,以便 wc 通过。

我也偶然发现了

Https://github.com/crioux/turbo-linecount

这很好。

你可以使用下面的方法,而且非常快:

wc -l filename #assume file got 50 lines then output -> 50 filename

此外,如果您只想获取数字而不显示文件名。你可以表演这个魔术。这将只获得文件中的行数,而不显示其名称。

wc -l filename | cut -f1 -d ' ' #space will be delimiter hence output -> 50