合并2个非常大的文本文件,更新每一行,而不使用内存

时间:2011-08-29 22:16:37

标签: php python memory file-io merge

假设我有两个文本文件,每个文件大约有200万行(每个文件大小约为50-80MB)。两个文件的结构是相同的:

Column1 Column2 Column3
...

第1列永远不会更改,第2列:两个文件中的相同值可能不同,并且两个文件的顺序不同, Column3 是一个数字,在每个文件中都会有所不同。

我需要能够将它们合并到一个文件中,与第2列匹配。如果两个文件中都存在Column2,则通过将两个文件中的Column3的值相加来更新Column3。

如果文件不是那么庞大,我可以通过将两个文件的每一行读入数组并从那里开始,在PHP中轻松完成此操作,但这样做很容易使可用内存过载。

有没有办法在不将每一行加载到内存的情况下执行此操作?我对PHP很熟悉,但如果它们不是太复杂而无法理解,则可以使用Python,Java或Shell脚本。

5 个答案:

答案 0 :(得分:2)

我会使用命令行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)
"

答案 1 :(得分:1)

好的,所以如果我正确读到这个,你就会:

文件1:

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用作排序键。这会降低缓存大小,因为您已经知道是否已经看到匹配的值以及何时转储早期的缓存数据。

答案 2 :(得分:1)

可能会让你失望的是你在看两个文件。没有必要这样做。要使用Mark的优秀示例: 文件1:

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的第二周将其降低到最终形式。

答案 3 :(得分:1)

你可以使用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()

答案 4 :(得分:0)

PHP的memory_limit适用于其主要的Web服务器脚本任务。批量处理数据非常不合适,比如您正在尝试的工作。问题是PHP的配置memory_limit,而不是你想要做一些需要“太多”内存的东西。我的手机拥有足够的内存,可以将2个80Mb文件加载到内存中,并以快速/简单的方式执行此操作,更不用说任何能够加载千兆字节(或至少1 GB)的真实计算机数据没有出汗。

显然你可以在运行时使用ini_set设置PHP的memory_limit(按照今天的标准是任意的,非常小的),仅适用于此脚本。你知道服务器上实际有多少内存吗?我知道很多共享托管服务提供商确实按照今天的标准为您提供了非常少量的内存,因为他们不希望您做的不仅仅是处理网页请求。但你可能只是按照你想要的方式直接在PHP中执行此操作,而不会跳过箍(并大大减慢进程)以避免一次性将所有文件加载到内存中。