获取文本文件的第一行和最后一行的最有效方法是什么?

pas*_*ino 69 python file seek

我有一个文本文件,每行包含一个时间戳.我的目标是找到时间范围.所有时间都是有序的,所以第一行将是最早的时间,最后一行将是最晚的时间.我只需要第一行和最后一行.在python中获取这些行的最有效方法是什么?

注意:这些文件的长度相对较大,每个大约1-2万行,我必须为几百个文件执行此操作.

Tra*_*asp 72

您可以打开文件进行读取并使用内置函数读取第一行readline(),然后搜索到文件末尾并向后退,直到找到该行的前一个EOL并从那里读取最后一行.

with open(file, "rb") as f:
    first = f.readline()        # Read the first line.
    f.seek(-2, os.SEEK_END)     # Jump to the second last byte.
    while f.read(1) != b"\n":   # Until EOL is found...
        f.seek(-2, os.SEEK_CUR) # ...jump back the read byte plus one more.
    last = f.readline()         # Read last line.
Run Code Online (Sandbox Code Playgroud)

跳转到倒数第二个字节而不是最后一个字节会阻止您因尾随EOL而直接返回.当你向后退时,你也会想要两个字节,因为阅读和检查EOL会将位置向前推进一步.

使用seek格式时fseek(offset, whence=0),whence表示偏移量相对于何处.从docs.python.org引用:

  • SEEK_SET0=从流的开头寻找(默认); offset必须是TextIOBase.tell()返回的 数字,或者为零.任何其他偏移值都会产生未定义的行为.
  • SEEK_CUR1="寻求"到当前位置; offset必须为零,这是一个无操作(所有其他值都不受支持).
  • SEEK_END2=寻求到流的末尾; offset必须为零(不支持所有其他值).

在一个文件中运行10k次,总共200kB的6k行,与之前建议的for循环相比,给出了1.62s vs 6.92s.使用1.3GB大小的文件,仍然有6k行,一百次导致8.93对86.95.

with open(file, "rb") as f:
    first = f.readline()     # Read the first line.
    for last in f: pass      # Loop through the whole file reading it all.
Run Code Online (Sandbox Code Playgroud)

  • 同样用于记录:如果你得到异常`io.UnsupportedOperation:不能做非零的终端相对搜索`,你必须分两步完成:首先找到文件的长度,然后添加偏移,然后通过到`f.seek(size + offset,os.SEEK_SET)` (4认同)
  • 这是最简洁的解决方案,我喜欢它.关于不猜测块大小的好处是它适用于小型测试文件.我添加了几行并将其包装在一个我喜欢称之为`tail_n`的函数中. (3认同)
  • 没关系,文件是空的,derp.无论如何最好的答案.+1 (2认同)
  • 根据[this comment](http://stackoverflow.com/a/31460631)作为答案,这个`而f.read(1)!="\n":``应该是`而f.read(1) != b"\n":` (2认同)

Sil*_*ost 58

适用于io模块的文档

with open(fname, 'rb') as fh:
    first = next(fh).decode()

    fh.seek(-1024, 2)
    last = fh.readlines()[-1].decode()
Run Code Online (Sandbox Code Playgroud)

这里的变量值是1024:它表示平均字符串长度.我只选择1024例如.如果您估计平均线长度,则可以使用该值乘以2.

由于您不知道行长度的可能上限,显而易见的解决方案是循环遍历文件:

for line in fh:
    pass
last = line
Run Code Online (Sandbox Code Playgroud)

你不需要打扰你可以使用的二进制标志open(fname).

ETA:由于您有许多文件可供使用,您可以使用random.sample并在其上运行此代码来创建几个文件的样本,以确定最后一行的长度.具有位置偏移的先验大值(假设1 MB).这将帮助您估算完整运行的值.

  • 使用`fh.seek(-1024,os.SEEK_END)`而不是`fh.seek(-1024,2)`可以提高可读性. (17认同)
  • 以下是不正确的: *你不需要为二进制标志而烦恼,你可以只使用 `open(fname)`。* 用 `b` 标志打开是至关重要的。如果你使用 `open(fname)` 而不是 `open(fname, 'rb')` 你会得到](/sf/ask/1507337401/ -unsupported-exception) *io.UnsupportedOperation: 不能做非零端相对搜索*。 (3认同)

mik*_*1aj 24

这是SilentGhost答案的修改版本,可以满足您的需求.

with open(fname, 'rb') as fh:
    first = next(fh)
    offs = -100
    while True:
        fh.seek(offs, 2)
        lines = fh.readlines()
        if len(lines)>1:
            last = lines[-1]
            break
        offs *= 2
    print first
    print last
Run Code Online (Sandbox Code Playgroud)

这里不需要行长的上限.


小智 9

你可以使用unix命令吗?我认为使用head -1并且tail -n 1可能是最有效的方法.或者,您可以使用简单fid.readline()来获取第一行fid.readlines()[-1],但这可能会占用太多内存.

  • 如果你有unix那么`os.popen("tail -n 1%s"%filename).read()`很好地得到最后一行. (10认同)

Mar*_*lla 6

这是我的解决方案,也与Python3兼容.它也管理边界案例,但它错过了utf-16支持:

def tail(filepath):
    """
    @author Marco Sulla (marcosullaroma@gmail.com)
    @date May 31, 2016
    """

    try:
        filepath.is_file
        fp = str(filepath)
    except AttributeError:
        fp = filepath

    with open(fp, "rb") as f:
        size = os.stat(fp).st_size
        start_pos = 0 if size - 1 < 0 else size - 1

        if start_pos != 0:
            f.seek(start_pos)
            char = f.read(1)

            if char == b"\n":
                start_pos -= 1
                f.seek(start_pos)

            if start_pos == 0:
                f.seek(start_pos)
            else:
                char = ""

                for pos in range(start_pos, -1, -1):
                    f.seek(pos)

                    char = f.read(1)

                    if char == b"\n":
                        break

        return f.readline()
Run Code Online (Sandbox Code Playgroud)

这是Trasp 的回答AnotherParker 的评论的结果.