读取大文件时跳过一条长线以避免MemoryError?

时间:2017-11-08 19:39:33

标签: python out-of-memory bigdata

我需要扫描两个大的txt文件(包括 100GB,10亿行,几列)并取出某个列(写入新文件)。文件看起来像这样

ID*DATE*provider
1111*201101*1234
1234*201402*5678
3214*201003*9012
...

我的Python脚本是

N100 = 10000000   ## 1% of 1 billion rows
with open("myFile.txt") as f:
    with open("myFile_c2.txt", "a") as f2:
        perc = 0
        for ind, line in enumerate(f):   ## <== MemoryError
            c0, c1, c2  = line.split("*")
            f2.write(c2+"\n")
            if ind%N100 == 0: 
                print(perc, "%")
                perc+=1

现在上面的脚本对于一个文件运行良好,但在另一个文件中以62%运行。 MemoryError的错误消息为for ind, line in enumerate(f):。我在不同的服务器上用不同的RAM尝试了几次,错误是一样的,都是62%。我等了几个小时来监视RAM,当它达到62%时,它会爆炸到28GB(总共= 32GB)。所以我想在那个文件中有一条线太长了(可能没有以\n结束?)因此Python在尝试将其读入RAM时卡住了。

所以我的问题是,在我去我的数据提供者之前,我该怎么做才能检测到错误行并以某种方式绕过/跳过将其视为一条巨大的线?感谢任何建议!

编辑:

从'错误行'开始,该文件可能与另一行分隔符而不是\n一起混淆。如果是这种情况,我可以检测行sep并继续提取我想要的列,而不是扔掉它们吗?谢谢!

2 个答案:

答案 0 :(得分:1)

此(未经测试的)代码可能会解决您的问题。它将每次读取的输入限制为1,000,000字节,以减少其最大内存消耗。

请注意,此代码会从每行返回第一个百万个字符。如何处理长线还有其他可能性:

  • 返回前一百万个字符
  • 返回最后的百万个字符
  • 完全跳过该行,可选择记录该行,或
  • 提出异常。

#UNTESTED
def read_start_of_line(fp):
    n = int(1e6)
    tmp = result = fp.readline(n)
    while tmp and tmp[-1] != '\n':
        tmp = fp.readline(n)
    return result

N100 = 10000000   ## 1% of 1 billion rows
with open("myFile.txt") as f:
    with open("myFile_c2.txt", "a") as f2:
        perc = 0
        for ind, line in enumerate(iter(lambda: read_start_of_line(f), '')):
            c0, c1, c2  = line.split("*")
            f2.write(c2+"\n")
            if ind%N100 == 0:
                print(perc, "%")
                perc+=1

答案 1 :(得分:0)

指定最大块大小可以解决内存溢出问题,同时仍允许您处理整个文件。以下生成器函数可以帮助您:

def chunks(f, bufsize):
  while True:
    chunk = f.readline(bufsize)
    if not chunk:
      break
    yield chunk
    if chunk[-1] == "\n":
      break

def lines(path, bufsize):
  with open(path) as f:
    pos = -1
    while f.tell() > pos:
      pos = f.tell()
      c = chunks(f, bufsize)
      yield c
      for _ in c:
        pass

以下是如何仅读取每行中前20个字符的示例:

import itertools

for i, line in enumerate(lines("./core/scrape.js", 10)):
  print(i, end=": ")
  print(''.join(itertools.islice(line, 2)).rstrip())

输出类似于:

0: /**
1:  * Document scraper/
2:  *
3:  * @author Branden H
4:  * @license MIT
5:  *
6:  */
7:
8: var promise = requir
9: var fs = promise.pro
10: var _ = require("lod
11: var util = require("
12: const path = require