最有效的文件连接和重排方法

2 投票
3 回答
1644 浏览
提问于 2025-04-15 20:48

我正在处理几个文件,每个文件分成两部分,第一部分是几千行的头部,接下来是几千行的主体。我的问题是,我需要把这些文件合并成一个文件,要求所有的头部在最上面,下面是主体部分。

目前我使用了两个循环:一个循环用来提取所有的头部并写入文件,另一个循环用来写入每个文件的主体部分(我还使用了一个 tmp_count 变量来限制加载到内存中的行数,然后再写入文件)。

这样做速度比较慢——处理一个13GB的文件大约需要6分钟。有没有人能告诉我怎么优化这个过程,或者在Python中有没有更快的方法?

谢谢!

这是我的代码:

def cat_files_sam(final_file_name,work_directory_master,file_count):

    final_file = open(final_file_name,"w")

    if len(file_count) > 1:
        file_count=sort_output_files(file_count)

    # only for @ headers    
    for bowtie_file in file_count:
        #print bowtie_file
        tmp_list = []

        tmp_count = 0
        for line in open(os.path.join(work_directory_master,bowtie_file)):
            if line.startswith("@"):

            if tmp_count == 1000000:
                final_file.writelines(tmp_list)
                tmp_list = []
                tmp_count = 0

            tmp_list.append(line)
            tmp_count += 1

        else:
            final_file.writelines(tmp_list)
            break

    for bowtie_file in file_count:
        #print bowtie_file
        tmp_list = []

        tmp_count = 0
        for line in open(os.path.join(work_directory_master,bowtie_file)):
            if line.startswith("@"):
            continue
        if tmp_count == 1000000:
            final_file.writelines(tmp_list)
            tmp_list = []
            tmp_count = 0

        tmp_list.append(line)
        tmp_count += 1
        final_file.writelines(tmp_list)

    final_file.close()

3 个回答

0

你想写的代码里有两个明显的低效之处(这里说的不是你展示的代码):

  1. 在第一个主要的 for 循环里,你在不断累积大量的头部行,而不是直接把它们写出来。
  2. 在第二个主要的 for 循环中,你又一行一行地跳过文件的头部,而在第一个循环里你已经确定了头部的结束位置。可以参考一下 file.seek 和 file.tell
2

你觉得移动13Gb的数据需要多快呢?这个问题主要是跟输入输出(I/O)有关,而不是Python本身的问题。要让速度更快,就要减少输入输出的操作。这意味着你要么就只能接受现在的速度,要么就需要调整你工具链后面的部分,让它们直接处理文件,而不是一次性处理一个巨大的13Gb文件。

2

如果你有足够的磁盘空间,可以节省第二次跳过文件头的时间。除了最终要生成的文件外,还可以打开一个临时文件 temp_file,并进行以下操作:

import shutil

hdr_list = []
bod_list = []
dispatch = {True: (hdr_list, final_file), 
            False: (bod_list, temp_file)}

for bowtie_file in file_count:
    with open(os.path.join(work_directory_master,bowtie_file)) as f:
        for line in f:
            L, fou = dispatch[line[0]=='@']
            L.append(f)
            if len(L) == 1000000:
                fou.writelines(L)
                del L[:]

# write final parts, if any
for L, fou in dispatch.items():
    if L: fou.writelines(L)

temp_file.seek(0)
shutil.copyfileobj(temp_file, final_file)

这样做可以提高你程序的运行效率。你可以考虑调整现在写死的 1000000,或者干脆不使用列表,直接把每一行写入到合适的文件(最终文件或临时文件)中,这些都是值得测试的选项。不过,如果你的内存没有限制,那么这些调整可能影响不大——不过,关于性能的直觉往往会让人误解,所以最好还是实际测试一下效果!

撰写回答