假设我有两个文本文件,每个文件大约有200万行(每个文件大小约为50-80MB)。两个文件的结构是相同的:
Column1 Column2 Column3
...
列1永远不会改变,列2:相同的值可能不在两个文件中,并且在两个文件中的顺序也不会相同,Column3是一个数字,在每个文件中都会不同。
我需要能够将它们合并到一个文件中,由列2匹配。如果两个文件中都存在Column2,则通过将两个文件中Column3的值相加来更新Column3。
如果文件不是那么大,我可以很容易地在PHP中做到这一点,通过将两个文件的每一行读入数组并从那里开始,但是这样做很容易使可用内存过载。
是否有一种方法可以做到这一点,而无需将每一行加载到内存中?我主要熟悉PHP,但如果Python、Java或Shell脚本不是太复杂而难以理解,我也可以使用它们。
我会使用命令行sort(1)
来合并和排序文件。之后,它应该是一个简单的脚本来计算总和。我不懂PHP,所以我用python来举例:
sort -k2 <file1> <file2> | python -c "
import itertools,sys
allLines = (x.strip().split(' ') for x in sys.stdin)
groups = itertools.groupby(allLines, lambda x:x[1])
for k,lines in groups:
firstLine = iter(g).next()
print firstLine[0], firstline[1], sum(int(x[2]) for x in lines)
"
好的,如果我没看错的话,你会有:
file1:
abc 12 34
abc 56 78
abc 90 12
file2:
abc 90 87 <-- common column 2
abc 12 67 <---common column 2
abc 23 1 <-- unique column 2
输出应该是:
abc 12 101
abc 90 99
如果是这种情况,那么像这样(假设它们是。csv格式):
$f1 = fopen('file1.txt', 'rb');
$f2 = fopen('file2.txt', 'rb');
$fout = fopen('outputxt.');
$data = array();
while(1) {
if (feof($line1) || feof($line2)) {
break; // quit if we hit the end of either file
}
$line1 = fgetcsv($f1);
if (isset($data[$line1[1]])) {
// saw the col2 value earlier, so do the math for the output file:
$col3 = $line1[2] + $data[$line1[1]];
$output = array($line[0], $line1[1], $col3);
fputcsv($fout, $output);
unset($data[$line1[1]]);
} else {
$data[$line1[1]] = $line1; // cache the line, if the col2 value wasn't seen already
}
$line2 = fgetcsv($f2);
if (isset($data[$line2[1]])) {
$col3 = $data[$line2[1]] + $line2[2];
$newdata = array($line2[0], $line2[1], $col3);
fputcsv($fout, $newdata);
unset($data[$line2[1]]); // remove line from cache
} else {
$data[$line2[1]] = $line2;
}
}
fclose($f1);
fclose($f2);
fclose($fout);
这是我的头,没有测试,可能不会工作,YMMV,等等…
如果预先对两个输入文件进行排序,那么使用column2作为排序键,将极大地简化事情。这样可以减小缓存大小,因为您可以知道是否已经看到了匹配的值,以及何时转储早期缓存的数据。
让您感到困惑的是,您正在查看两个文件。没必要这么做。用马克的例子:file1:
abc 12 34
abc 56 78
abc 90 12
file2:
abc 90 87
abc 12 67
abc 23 1
然后sort file1 file2 > file3
收益率file3:
abc 12 34
abc 12 67
abc 23 1
abc 56 78
abc 90 12
abc 90 87
CS-101的第二周,将其简化为最终形式
您可以使用Python sqlite3包含模块轻松解决此问题,而无需使用太多内存(大约13 Mb, 100万行):
import sqlite3
files = ("f1.txt", "f2.txt") # Files to compare
# # Create test data
# for file_ in files:
# f = open(file_, "w")
# fld2 = 0
# for fld1 in "abc def ghi jkl".split():
# for fld3 in range(1000000 / 4):
# fld2 += 1
# f.write("%s %s %s'n" % (fld1, fld2, 1))
#
# f.close()
sqlite_file = "./join.tmp" # or :memory: if you don't want to create a file
cnx = sqlite3.connect(sqlite_file)
for file_ in range(len(files)): # Create & load tables
table = "file%d" % (file_+1)
cnx.execute("drop table if exists %s" % table)
cnx.execute("create table %s (fld1 text, fld2 int primary key, fld3 int)" % table)
for line in open(files[file_], "r"):
cnx.execute("insert into %s values (?,?,?)" % table, line.split())
# Join & result
cur = cnx.execute("select f1.fld1, f1.fld2, (f1.fld3+f2.fld3) from file1 f1 join file2 f2 on f1.fld2==f2.fld2")
while True:
row = cur.fetchone()
if not row:
break
print row[0], row[1], row[2]
cnx.close()
PHP的memory_limit适用于web服务器脚本的主要任务。它非常不适合批处理数据,比如您正在尝试做的工作。问题是PHP配置的memory_limit,而不是您试图做一些需要"太多"内存的事情。我的手机有足够的内存,只需加载2 80Mb的文件到内存中,并且以快速/简单的方式完成此操作,更不用说任何类型的真正的计算机应该能够轻松加载千兆字节(或至少1gb)的数据。
显然,您可以在运行时使用ini_set
设置PHP的memory_limit(按照今天的标准,这是任意的,非常小),仅用于此脚本。您知道服务器上实际有多少可用内存吗?我知道按照今天的标准,很多共享主机提供商会给你非常少的内存,因为他们不希望你做比处理网页请求更多的事情。但是,您可以直接在PHP中按照您想要的方式执行此操作,而无需跳过各种步骤(并大大减慢进程),以避免一次将所有文件加载到内存中。