【问题标题】:Skip a long line when reading a big file to avoid MemoryError?读取大文件时跳过长行以避免 MemoryError?
【发布时间】:2017-11-08 19:39:33
【问题描述】:

我需要扫描两个大的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%。错误消息为for ind, line in enumerate(f): 显示MemoryError。我在不同RAM的不同服务器上尝试了几次,错误是一样的,都是62%。我等了几个小时来监控 RAM,当 62% 时它爆炸到 28GB(总计 = 32GB)。所以我猜在那个文件中有一行太长了(可能没有以\n 结尾?),因此 Python 在尝试将其读取到 RAM 时卡住了。

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

编辑:

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

【问题讨论】:

  • 如果你监控前62%的进程,内存使用是否稳定增长?您可能有与行解析无关的内存泄漏。
  • 您可以在文件对象上使用readline 方法,该方法接受最大行长度。
  • 其实没有。在接近 62% 之前一切都很好
  • 然后放弃“逐行”方法,逐个字符读取,计算“*”和换行符。
  • @Jean-FrançoisFabre 或一些合理的“块”。

标签: python out-of-memory bigdata


【解决方案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

【讨论】:

  • 你能解释一下while tmp and tmp[-1] != '\n':是做什么的吗?看来 1,000,000 字节会将好的行切成碎片,那我怎样才能得到我想要的列呢?谢谢
  • 如果 c2 在行尾怎么办?也许保持行的 end 会更好。这取决于OP“大线”中的内容
  • @JasonLou - 该表达式控制while 循环的延续。它继续读取并丢弃数据,直到它最终读取一个换行符,即直到它最终读取整个长行。
  • @Jean-FrançoisFabre - 你是对的。我的假设是 c2 在前 1,000,000 个字符内。我会记录下来的。
  • 因为该行被分成 3 部分(解包需要),如果 c2 在前 10000000 个字符内,那么它会被您的方法截断。如果它在最后 10000000 个字符中,则可能会截断 c0 或/和 c1,但不会截断 c2(否则:解包错误)。我会逐块阅读并手动拆分。但编写这样的代码需要付出很大的努力。有趣的问题,但回答这个问题的工作很乏味。
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 2019-04-07
    • 2020-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-06
    • 1970-01-01
    • 2016-11-22
    相关资源
    最近更新 更多