使用Python拆分大文件

12

我想尝试将大文件(比如约10GB)拆分成多个小文件。基本思路是读取每一行,并将每40000行分为一个文件组。 但是有两种“读取”文件的方式。

1)第一种方法是一次性读取整个文件,并将其转换为列表。但是这需要将整个文件加载到内存中,对于太大的文件而言会很痛苦。(我之前好像问过这样的问题) 在Python中,我尝试过的一次性读取整个文件的方法包括:

input1=f.readlines()

input1 = commands.getoutput('zcat ' + file).splitlines(True)

input1 = subprocess.Popen(["cat",file],
                              stdout=subprocess.PIPE,bufsize=1)

那么,我可以通过以下方式轻松将40000行分组为一个文件:list[40000,80000]或list[80000,120000]。 使用列表的优点是我们可以轻松地指向特定的行。

2)第二种方法是逐行读取。在读取时处理该行。这些读取的行不会保存在内存中。 例如:

f=gzip.open(file)
for line in f: blablabla...
for line in fileinput.FileInput(fileName):

我确定在gzip.open中,这个f不是一个列表,而是一个文件对象。而且似乎我们只能逐行处理;那么如何执行这个“split”任务呢?如何指向文件对象的特定行?

谢谢


当你思考它时,你无法知道当前所在的行数。只有在阅读了所有前面的行并计算了换行符(\n)后,才能确定当前所在的行数。(忽略特殊情况,即每行长度已知的奇怪文件。) - rplnt
8个回答

21
NUM_OF_LINES=40000
filename = 'myinput.txt'
with open(filename) as fin:
    fout = open("output0.txt","wb")
    for i,line in enumerate(fin):
      fout.write(line)
      if (i+1)%NUM_OF_LINES == 0:
        fout.close()
        fout = open("output%d.txt"%(i/NUM_OF_LINES+1),"wb")

    fout.close()

如果你想要文件中恰好有40,000行,我认为你应该将i初始化为0而不是1 - martineau
你需要哪些软件包? - L F
@LuisFelipe 不需要外部包,fileinput 是一个内置的包,甚至对于这个功能也不是必需的,你同样可以使用普通的 open() - yurib
我尝试了相同的代码,它显示“名称'filename'未定义”。 - L F
1
@LuisFelipe filename 是一个变量,应该包含您的输入文件路径。 - yurib
@yurib 抱歉,我在评论其他问题哈哈。 - L F

5
如果每个文件中具有特定数量的文件行没有什么特别之处,那么 readlines() 函数 还接受一个大小“提示”参数,其行为如下:
如果给出可选参数sizehint,则从文件中读取指定数量的字节和足以完成一行的其他内容,并返回这些行。这通常用于通过行有效地读取大型文件,但无需将整个文件加载到内存中。只有完整的行才会被返回。
...因此,您可以像这样编写代码:
# assume that an average line is about 80 chars long, and that we want about 
# 40K in each file.

SIZE_HINT = 80 * 40000

fileNumber = 0
with open("inputFile.txt", "rt") as f:
   while True:
      buf = f.readlines(SIZE_HINT)
      if not buf:
         # we've read the entire file in, so we're done.
         break
      outFile = open("outFile%d.txt" % fileNumber, "wt")
      outFile.write(buf)
      outFile.close()
      fileNumber += 1 

-1 (1) 您没有明确关闭输出文件 (2) 在文本模式下读取并在二进制模式下写入,如果我们在Windows上,则保证会“捣乱”。 - John Machin
'hint'参数在此处有详细说明:https://docs.python.org/3/library/io.html?highlight=readlines#io.IOBase.readlines - 答案中的链接似乎不再提及它了。 - natka_m

4
我找到的最佳解决方案是使用库filesplit
您只需要指定输入文件、输出文件夹以及输出文件的所需大小(以字节为单位)。最后,该库将为您完成所有工作。
from fsplit.filesplit import Filesplit

def split_cb(f, s):
    print("file: {0}, size: {1}".format(f, s))

fs = Filesplit()
fs.split(file="/path/to/source/file", split_size=900000, output_dir="/pathto/output/dir", callback=split_cb)

使用打印回调监视生成的文件及其大小非常有用。这似乎是旧版本2.0具有Filesplit().split()方法。当前版本4.0使用Split().bysize()。请参见我的答案 - hc_dev

3
chunk_size = 40000
fout = None
for (i, line) in enumerate(fileinput.FileInput(filename)):
    if i % chunk_size == 0:
        if fout: fout.close()
        fout = open('output%d.txt' % (i/chunk_size), 'w')
    fout.write(line)
fout.close()

在退出循环后,您需要执行 if fout: fout.close() - John Machin

3
对于一个10GB文件,第二种方法显然是更好的选择。下面是你需要做的步骤:
  1. 打开输入文件。
  2. 打开第一个输出文件。
  3. 从输入文件中读取一行并将其写入输出文件。
  4. 维护一个计数器来记录你已经写入到当前输出文件中的行数;一旦它达到40000,关闭输出文件,并打开下一个文件。
  5. 重复步骤3-4,直到到达输入文件的末尾。
  6. 关闭两个文件。

if num_lines % 4000 == 0: avoid_writing_empty_file_at_end() # except when numlines == 0 - John Machin

0
我创建了这个小脚本,可以在几秒钟内拆分大文件。仅用了20秒就将一个有2000万行的文本文件拆分成10个每个有200万行的小文件。
split_length = 2_000_000
file_count = 0
large_file = open('large-file.txt', encoding='utf-8', errors='ignore').readlines()

for index in range(0, len(large_file)):
    if (index > 0) and (index % 2000000 == 0):
        new_file = open(f'splitted-file-{file_count}.txt', 'a', encoding='utf-8', errors='ignore')
        split_start_value = file_count * split_length
        split_end_value = split_length * (file_count + 1)
        file_content_list = large_file[split_start_value:split_end_value]
        file_content = ''.join(line for line in file_content_list)
        new_file.write(file_content)
        new_file.close()
        file_count += 1
        print(f'created file {file_count}')

0

按行分割文件:

将每40000行分组成一个文件

您可以使用模块filesplit的方法bylinecount(版本4.0):

import os
from filesplit.split import Split

LINES_PER_FILE = 40_000  # see PEP515 for readable numeric literals 
filename = 'myinput.txt'
outdir = 'splitted/'  # to store split-files `myinput_1.txt` etc.

Split(filename, outdir).bylinecount(LINES_PER_FILE)

这类似于 rafaoc的答案,显然使用了过时的 2.0 版本按大小拆分。


0

显然,由于您正在对文件进行操作,您需要以某种方式迭代文件的内容--无论是手动执行还是让Python API的一部分为您执行(例如readlines()方法)都不重要。在大O分析中,这意味着您将花费O(n)时间(n为文件大小)。

但是,将文件读入内存也需要O(n)空间。虽然有时我们确实需要将10 GB的文件读入内存,但您的特定问题并不需要这样做。我们可以直接迭代文件对象。当然,文件对象确实需要空间,但我们没有理由以两种不同的形式保存文件内容两次。

因此,我会选择您的第二个解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接