【问题标题】:How to efficiently remove the first line of a large file?如何有效地删除大文件的第一行?
【发布时间】:2025-11-27 23:35:01
【问题描述】:

herehere 已经提出过这个问题,但没有一个解决方案适合我。

如何在 Python 3 中有效地从大文件中删除 first 行?

我正在编写一个需要记录的程序,并且日志文件具有可配置的最大大小,可以是无限的。因此,我不想使用readlines() 或类似的方法,因为这些方法会占用大量内存。速度不是一个大问题,但如果它可以在重写整个文件并且没有临时文件的情况下完成,那就太好了。

解决方案需要跨平台。

示例日志文件:

[09:14:56 07/04/17] [INFO] foo
[23:45:01 07/04/17] [WARN] bar
[13:45:28 08/04/17] [INFO] foobar
... many thousands more lines

输出:

[23:45:01 07/04/17] [WARN] bar
[13:45:28 08/04/17] [INFO] foobar
... many thousands more lines

这段代码将循环运行:

while os.path.getsize(LOGFILE) > MAXLOGSIZE:
    # remove first line of file

以下解决方案均无效且内存效率高:

解决方案 #1 - 有效但效率低

with open('file.txt', 'r') as fin:
    data = fin.read().splitlines(True)
with open('file.txt', 'w') as fout:
    fout.writelines(data[1:])

解决方案 #2 - 不起作用,文件为空

import shutil

source_file = open('file.txt', 'r')
source_file.readline()
target_file = open('file.txt', 'w')

shutil.copyfileobj(source_file, target_file)

解决方案 #3 - 有效,但使用额外的文件:

with open("file.txt",'r') as f:
    with open("new_file.txt",'w') as f1:
        f.next() # skip header line
        for line in f:
            f1.write(line)

【问题讨论】:

    标签: python python-3.x logging file-io


    【解决方案1】:

    所以,这种方法非常 hacky。如果您的线条尺寸大致相同且标准偏差较小,它将很好地工作。这个想法是将文件的某些部分读入一个足够小以提高内存效率但又足够大以使两端写入不会弄乱的缓冲区中(因为行的大小大致相同,差异很小,我们可以交叉我们的手指并祈祷它会起作用)。我们基本上跟踪我们在文件中的位置并来回跳转。我使用collections.deque 作为缓冲区,因为它在两端都有良好的append 性能,我们可以利用队列的先进先出特性:

    from collections import deque
    def efficient_dropfirst(f, dropfirst=1, buffersize=3):
        f.seek(0)
        buffer = deque()
        tail_pos = 0
        # these next two loops assume the file has many thousands of
        # lines so we can safely drop and buffer the first few...
        for _ in range(dropfirst):
            f.readline()
        for _ in range(buffersize):
            buffer.append(f.readline())
        line = f.readline()
        while line:
            buffer.append(line)
            head_pos = f.tell()
            f.seek(tail_pos)
            tail_pos += f.write(buffer.popleft())
            f.seek(head_pos)
            line = f.readline()
        f.seek(tail_pos)
        # finally, clear out the buffer:
        while buffer:
            f.write(buffer.popleft())
        f.truncate()
    

    现在,让我们用一个表现良好的假冒文件来试试这个:

    >>> s = """1. the quick
    ... 2. brown fox
    ... 3. jumped over
    ... 4. the lazy
    ... 5. black dog.
    ... 6. Old McDonald's
    ... 7. Had a farm
    ... 8. Eeyi Eeeyi Oh
    ... 9. And on this farm they had a
    ... 10. duck
    ... 11. eeeieeeiOH
    ... """
    

    最后:

    >>> import io
    >>> with io.StringIO(s) as f: # we mock a file
    ...     efficient_dropfirst(f)
    ...     final = f.getvalue()
    ...
    >>> print(final)
    2. brown fox
    3. jumped over
    4. the lazy
    5. black dog.
    6. Old McDonald's
    7. Had a farm
    8. Eeyi Eeeyi Oh
    9. And on this farm they had a
    10. duck
    11. eeeieeeiOH
    

    如果dropfirst buffersize 稍微“松懈”,这应该可以解决。由于您只想删除第一行,因此请保留dropfirst=1,并且您可以制作buffersize=100 或其他内容以确保安全。它比阅读“数千行”的内存效率高得多,如果没有一行比前面的行大,你应该是安全的。但请注意,这是非常粗糙的边缘。

    【讨论】:

    • 经过广泛的测试,这似乎 100% 的时间有效。从代码看来,您所说的应该是正确的 - 它的行为应该不可靠。但出乎意料的可靠性对我来说很好!
    • @retnikt 如果您强制执行行长度(填写未到达末尾的位置,当它超过时创建一个新行),那么您可以获得可靠的行为。这可能比它的价值更麻烦
    • 所以,举一个它不会工作的例子,比如说有很长的一行,大约 len > 200,而之前有 100 行只有一个字符(即换行符),那么它将失败,而且不漂亮。
    • 这就是你的意思。我误解了你。对不起。
    【解决方案2】:

    试试这个。它使用您提到的第三种方法,但不会创建新文件。

    filePath = r"E:\try.txt"
    file_str = ""
    with open(filePath,'r') as f:
            f.next()  # skip header line
            for line in f:
                file_str = file_str + line
    
    with open(filePath, "w") as f:
        f.write(file_str)
    

    【讨论】:

    • 这不是内存高效的解决方案。另外,我收到此错误:AttributeError: '_io.TextIOWrapper' object has no attribute 'next'。那是因为它是第 3 方解决方案并且需要其他模块吗?
    • @retnikt 不,这是因为在 python 3 中你需要使用 next(f) 而不是 f.next()
    • 这不是大文件的解决方案。您的脚本将失败,因为将使用所有内存。
    最近更新 更多