读取巨型文件中单行的最快方法
Fastest way read single line in giant file
所以我有一个网站,我需要访问一个巨大的文本文件 (~2GB) 中的一行(行号已知)。
我得出的结论是
system_exec("sed -n 3p << /file/whatever.txt");
在PHP中是最有效的方法。
但我觉得使用起来不是很舒服,它看起来很糟糕而且不安全。真的可以用吗?如果没有 PHP 框架,这在某种程度上是可能的吗?还是有更有效的方法来做到这一点?
在巨型文件中打印单行的最快方式是使用 q(退出)命令进行少量修改
sed -n '3{p;q}' yourFile
这将打印第 3 行,然后 sed 将停止工作。
这里有多种方法可以偏移到文件中,以及一些粗略的基准测试。
我创建了一个 90M 行的文本文件。每行包含 'something#####',但数字与实际行不匹配(以便更快地创建示例数据)。
$ wc bigfile.txt
90000000 90000000 1340001000 bigfile.txt
$ ls -lrth bigfile.txt
-rw-rw-r-- 1 admin wheel 1.2G Mar 8 09:37 bigfile.txt
这些基准测试是在 1.3GHz i5、4GB RAM、MacBook Air(11 英寸,2013 年中)运行 OS 10.10.2.
上执行的
首先是 awk
。我真的期待更好。
$ time awk 'NR == 10000000{print;exit}' bigfile.txt
something99999
real 0m12.716s
user 0m12.529s
sys 0m0.117s
tail
表现好一点,但仍然很慢。
$ time tail -n +10000000 bigfile.txt | head -n 1
something99999
real 0m10.393s
user 0m10.311s
sys 0m0.066s
正如您所发现的,sed
由于某种原因,到目前为止,其表现优于其他竞争者。虽然,仍然慢得令人无法接受。
$ time sed -n '10000000{p;q;}' bigfile.txt
something99999
real 0m3.846s
user 0m3.772s
sys 0m0.053s
如果你有常规数据(每行字节数相同或可以确定地计算每行字节数),你可以完全放弃读取文件并直接偏移到文件中。这是最快的选项,但在数据格式方面也是最受限制的。这就是 William Pursell 在建议将数据填充到固定大小时的意思。
$ time tail -c +10000000 bigfile.txt | head -n 1
thing71851
real 0m0.020s
user 0m0.011s
sys 0m0.006s
但是,如果您有一个 2G 的文本文件,您应该考虑使用合适的数据库。
$ time sqlite3 bigfile.db << EOF
> create table bigdb(data text);
> .import bigfile.txt bigdb
> EOF
real 3m16.650s
user 3m3.703s
sys 0m4.221s
$ ls -lrth bigfile.db
-rw-r--r-- 1 admin wheel 1.9G Mar 8 10:16 bigfile.db
既然你有了数据库,你应该能够获得极快的速度吧?只有你正确使用它。 OFFSET
(LIMIT
的第一个参数)因慢得离谱而臭名昭著,应避免使用。
$ time sqlite3 bigfile.db <<< 'select * from bigdb limit 10000000-1, 1;'
something99999
real 0m2.156s
user 0m0.688s
sys 0m0.440s
您应该有一个合适的主键,或者使用 sqlite 方便的内部列 ROWID
以获得最佳性能。
$ time sqlite3 bigfile.db <<< 'select * from bigdb where ROWID == 10000000;'
something99999
real 0m0.017s
user 0m0.003s
sys 0m0.005s
在我的系统上我有一个完全不同的结论
环境:KSH
下的AIX
FileName=listOfBig.txt
# ls -l -> 239.070.208 bytes
# wc -l listOfBig.txt | read FileSize Ignore
FileSize=638976
# take a portion of 8 lines at 1000 lines of the end
LineToStart=$(( ${FileSize} - 1024 ))
LineToTake=8
LineToStop=$(( ${LineToStart} + ${LineToTake} - 1 ))
time sed -n "${LineToStart},${LineToStop} p;${LineToStop} q" ${FileName} >/dev/null
real 0m1.49s
user 0m0.45s
sys 0m0.41s
time sed "${LineToStart},${LineToStop} !d;${LineToStop} q" ${FileName} >/dev/null
real 0m1.51s
user 0m0.45s
sys 0m0.42s
time tail -n +${LineToStart} ${FileName} | head -${LineToTake} >/dev/null
real 0m0.34s
user 0m0.00s
sys 0m0.00s
time head -${LineToStop} ${FileName} | tail -${LineToTake} >/dev/null
real 0m0.84s
user 0m0.75s
sys 0m0.23s
第一个(缓存,...)的第二个和后续测试肯定有一个小优势,但差别不大
因此,在此测试中,sed 慢很多(不是 linux 上的 GNU 版本的工具)。
另一个无法解释的问题是大文件(可能发生在小文件上但很少发生)是文件更改时管道流的问题(通常是日志中的情况)。我曾经遇到过问题,应该创建一个临时文件(也非常大)来处理该行的其他请求(如果有的话)。
所以我有一个网站,我需要访问一个巨大的文本文件 (~2GB) 中的一行(行号已知)。
我得出的结论是
system_exec("sed -n 3p << /file/whatever.txt");
在PHP中是最有效的方法。
但我觉得使用起来不是很舒服,它看起来很糟糕而且不安全。真的可以用吗?如果没有 PHP 框架,这在某种程度上是可能的吗?还是有更有效的方法来做到这一点?
在巨型文件中打印单行的最快方式是使用 q(退出)命令进行少量修改
sed -n '3{p;q}' yourFile
这将打印第 3 行,然后 sed 将停止工作。
这里有多种方法可以偏移到文件中,以及一些粗略的基准测试。
我创建了一个 90M 行的文本文件。每行包含 'something#####',但数字与实际行不匹配(以便更快地创建示例数据)。
$ wc bigfile.txt
90000000 90000000 1340001000 bigfile.txt
$ ls -lrth bigfile.txt
-rw-rw-r-- 1 admin wheel 1.2G Mar 8 09:37 bigfile.txt
这些基准测试是在 1.3GHz i5、4GB RAM、MacBook Air(11 英寸,2013 年中)运行 OS 10.10.2.
上执行的首先是 awk
。我真的期待更好。
$ time awk 'NR == 10000000{print;exit}' bigfile.txt
something99999
real 0m12.716s
user 0m12.529s
sys 0m0.117s
tail
表现好一点,但仍然很慢。
$ time tail -n +10000000 bigfile.txt | head -n 1
something99999
real 0m10.393s
user 0m10.311s
sys 0m0.066s
正如您所发现的,sed
由于某种原因,到目前为止,其表现优于其他竞争者。虽然,仍然慢得令人无法接受。
$ time sed -n '10000000{p;q;}' bigfile.txt
something99999
real 0m3.846s
user 0m3.772s
sys 0m0.053s
如果你有常规数据(每行字节数相同或可以确定地计算每行字节数),你可以完全放弃读取文件并直接偏移到文件中。这是最快的选项,但在数据格式方面也是最受限制的。这就是 William Pursell 在建议将数据填充到固定大小时的意思。
$ time tail -c +10000000 bigfile.txt | head -n 1
thing71851
real 0m0.020s
user 0m0.011s
sys 0m0.006s
但是,如果您有一个 2G 的文本文件,您应该考虑使用合适的数据库。
$ time sqlite3 bigfile.db << EOF
> create table bigdb(data text);
> .import bigfile.txt bigdb
> EOF
real 3m16.650s
user 3m3.703s
sys 0m4.221s
$ ls -lrth bigfile.db
-rw-r--r-- 1 admin wheel 1.9G Mar 8 10:16 bigfile.db
既然你有了数据库,你应该能够获得极快的速度吧?只有你正确使用它。 OFFSET
(LIMIT
的第一个参数)因慢得离谱而臭名昭著,应避免使用。
$ time sqlite3 bigfile.db <<< 'select * from bigdb limit 10000000-1, 1;'
something99999
real 0m2.156s
user 0m0.688s
sys 0m0.440s
您应该有一个合适的主键,或者使用 sqlite 方便的内部列 ROWID
以获得最佳性能。
$ time sqlite3 bigfile.db <<< 'select * from bigdb where ROWID == 10000000;'
something99999
real 0m0.017s
user 0m0.003s
sys 0m0.005s
在我的系统上我有一个完全不同的结论 环境:KSH
下的AIXFileName=listOfBig.txt
# ls -l -> 239.070.208 bytes
# wc -l listOfBig.txt | read FileSize Ignore
FileSize=638976
# take a portion of 8 lines at 1000 lines of the end
LineToStart=$(( ${FileSize} - 1024 ))
LineToTake=8
LineToStop=$(( ${LineToStart} + ${LineToTake} - 1 ))
time sed -n "${LineToStart},${LineToStop} p;${LineToStop} q" ${FileName} >/dev/null
real 0m1.49s
user 0m0.45s
sys 0m0.41s
time sed "${LineToStart},${LineToStop} !d;${LineToStop} q" ${FileName} >/dev/null
real 0m1.51s
user 0m0.45s
sys 0m0.42s
time tail -n +${LineToStart} ${FileName} | head -${LineToTake} >/dev/null
real 0m0.34s
user 0m0.00s
sys 0m0.00s
time head -${LineToStop} ${FileName} | tail -${LineToTake} >/dev/null
real 0m0.84s
user 0m0.75s
sys 0m0.23s
第一个(缓存,...)的第二个和后续测试肯定有一个小优势,但差别不大
因此,在此测试中,sed 慢很多(不是 linux 上的 GNU 版本的工具)。
另一个无法解释的问题是大文件(可能发生在小文件上但很少发生)是文件更改时管道流的问题(通常是日志中的情况)。我曾经遇到过问题,应该创建一个临时文件(也非常大)来处理该行的其他请求(如果有的话)。