如何跳转到巨大文本文件中的特定行?

Posted

技术标签:

【中文标题】如何跳转到巨大文本文件中的特定行?【英文标题】:How to jump to a particular line in a huge text file? 【发布时间】:2010-10-11 20:18:00 【问题描述】:

下面的代码是否有替代方案:

startFromLine = 141978 # or whatever line I need to jump to

urlsfile = open(filename, "rb", 0)

linesCounter = 1

for line in urlsfile:
    if linesCounter > startFromLine:
        DoSomethingWithThisLine(line)

    linesCounter += 1

如果我正在处理一个巨大的文本文件(~15MB),其中行数未知但长度不同,并且需要跳转到我事先知道的特定行数?当我知道我至少可以忽略文件的前半部分时,我会通过一个一个地处理它们而感到难过。如果有的话,寻找更优雅的解决方案。

【问题讨论】:

你怎么知道文件的前 1/2 不是一堆 "\n" 而后半是一行?为什么你会为此感到难过? 我认为标题具有误导性 - 至少 15MB 并不是真正的“巨大文本文件”... 【参考方案1】:

@george 出色地建议了mmap,它大概使用了系统调用mmap。这是另一个版本。

import mmap

LINE = 2  # your desired line

with open('data.txt','rb') as i_file, mmap.mmap(i_file.fileno(), length=0, prot=mmap.PROT_READ) as data:
  for i,line in enumerate(iter(data.readline, '')):
    if i!=LINE: continue
    pos = data.tell() - len(line)
    break

  # optionally copy data to `chunk`
  i_file.seek(pos)
  chunk = i_file.read(len(line))

print(f'line i')
print(f'byte pos')
print(f'data line')
print(f'data chunk')

【讨论】:

【参考方案2】:

如果行的长度不同,您实际上并没有那么多选择……遗憾的是,您需要处理行尾字符才能知道何时进入下一行。

但是,您可以通过将最后一个参数“open”更改为非 0 的值来显着加快速度并减少内存使用量。

0 表示文件读取操作是无缓冲的,非常慢且占用大量磁盘空间。 1 表示文件是行缓冲的,这将是一个改进。任何大于 1(比如 8 kB,即 8192 或更高)的文件都会将文件块读入内存。您仍然可以通过for line in open(etc): 访问它,但python 一次只运行一点,在处理完每个缓冲块后丢弃它。

【讨论】:

我在这里做了一些测试,并将其设置为 -1(操作系统默认值,通常为 8k,但通常很难分辨),似乎与它获得的速度一样快。也就是说,部分原因可能是我正在虚拟服务器上进行测试。【参考方案3】:

我可能被丰富的 ram 宠坏了,但 15 M 并不大。使用readlines() 读入内存是我通常对这种大小的文件所做的。之后访问一行很简单。

【讨论】:

为什么我对读取整个文件有点犹豫——我可能有几个这样的进程正在运行,如果其中有十几个进程读取 12 个文件,每个文件 15MB,那就不好了。但我需要对其进行测试以了解它是否有效。谢谢。 @photographer:在典型的现代机器上,即使是读取 15MB 文件的“几个”进程也不重要(当然,这取决于你对它们所做的确切操作)。 雅各布,是的,我应该试试。如果 vm 没有崩溃,则进程在虚拟机上运行数周。不幸的是,上次它在 6 天后崩溃了。我需要从它突然停止的地方继续。仍然需要弄清楚如何找到它离开的地方。 @Noah:但事实并非如此!你为什么不走得更远?如果文件 128TB 怎么办?比许多操作系统都无法支持它。为什么不解决问题呢? @SilentGhost:我也希望得到一个可能对我有用的答案。我已经为我的文件拼凑了一个索引方案,范围从 100MB 到近 1GB,但更简单且不易出错的解决方案会很好。【参考方案4】:

这是一个使用readlines(sizehint) 一次读取一大块行的示例。 DNS 指出了该解决方案。我写这个例子是因为这里的其他例子都是单行的。

def getlineno(filename, lineno):
    if lineno < 1:
        raise TypeError("First line is line 1")
    f = open(filename)
    lines_read = 0
    while 1:
        lines = f.readlines(100000)
        if not lines:
            return None
        if lines_read + len(lines) >= lineno:
            return lines[lineno-lines_read-1]
        lines_read += len(lines)

print getlineno("nci_09425001_09450000.smi", 12000)

【讨论】:

【参考方案5】:

没有一个答案特别令人满意,所以这里有一个小sn-p来帮忙。

class LineSeekableFile:
    def __init__(self, seekable):
        self.fin = seekable
        self.line_map = list() # Map from line index -> file position.
        self.line_map.append(0)
        while seekable.readline():
            self.line_map.append(seekable.tell())

    def __getitem__(self, index):
        # NOTE: This assumes that you're not reading the file sequentially.  
        # For that, just use 'for line in file'.
        self.fin.seek(self.line_map[index])
        return self.fin.readline()

示例用法:

In: !cat /tmp/test.txt

Out:
Line zero.
Line one!

Line three.
End of file, line four.

In:
with open("/tmp/test.txt", 'rt') as fin:
    seeker = LineSeekableFile(fin)    
    print(seeker[1])
Out:
Line one!

这涉及执行大量文件查找,但对于无法将整个文件放入内存的情况很有用。它会进行一次初始读取以获取行位置(因此它会读取整个文件,但不会将其全部保存在内存中),然后每次访问都会在事后查找文件。

我根据用户的判断在 MIT 或 Apache 许可下提供上述 sn-p。

【讨论】:

这是最好的解决方案,不仅适用于问题,而且适用于读取大文件时的许多其他内存相关问题。谢谢你!【参考方案6】:

linecache:

linecache 模块允许人们从 Python 源文件中获取任何行,同时尝试使用缓存进行内部优化,这是从单个文件中读取多行的常见情况。 traceback 模块使用它来检索源代码行以包含在格式化的回溯中...

【讨论】:

我刚刚查看了这个模块的源代码:整个文件是在内存中读取的!因此,为了快速访问文件中的给定行,我肯定会排除这个答案。 MiniQuark,我试过了,它确实有效,而且速度非常快。我需要看看如果我以这种方式同时处理十几个文件会发生什么,找出我的系统在什么时候死机。 您的操作系统的虚拟内存管理器提供了相当多的帮助,因此如果您没有产生大量页面错误,则将大文件读入内存可能不会很慢 :) 相反,这样做“愚蠢的方式”并分配大量内存可以非常快。我喜欢丹麦 FreeBSD 开发人员 Poul-Henning Kamp 的文章:queue.acm.org/detail.cfm?id=1814327 试试 100G 文件,很烂。我必须使用 f.tell()、f.seek()、f.readline()【参考方案7】:

我很惊讶没有人提到 islice

line = next(itertools.islice(Fhandle,index_of_interest,index_of_interest+1),None) # just the one line

或者如果你想要整个文件的其余部分

rest_of_file = itertools.islice(Fhandle,index_of_interest)
for line in rest_of_file:
    print line

或者如果你想要文件中的每一行

rest_of_file = itertools.islice(Fhandle,index_of_interest,None,2)
for odd_line in rest_of_file:
    print odd_line

【讨论】:

【参考方案8】:

如果您正在处理 文本文件 并且基于 linux 系统,您可以使用 linux 命令。 对我来说,这很好用!

import commands

def read_line(path, line=1):
    return commands.getoutput('head -%s %s | tail -1' % (line, path))

line_to_jump = 141978
read_line("path_to_large_text_file", line_to_jump)

【讨论】:

当然不兼容windows或者一些不支持head/tail的linux shell。 这是否比在 Python 中更快? 这个可以多行吗?【参考方案9】:

可以使用这个函数返回第n行:

def skipton(infile, n):
    with open(infile,'r') as fi:
        for i in range(n-1):
            fi.next()
        return fi.next()

【讨论】:

如果有连续的空行,这个逻辑就不起作用了,fi.next() 会一次性跳过所有的空行,否则很好:) OP 没有提到这些行包含非标准换行符的行。在这种情况下,您必须使用至少一个 if 语句来解析每一行以用于部分换行符。【参考方案10】:

您可以使用 mmap 来查找行的偏移量。 MMap 似乎是处理文件最快的方法

示例:

with open('input_file', "r+b") as f:
    mapped = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
    i = 1
    for line in iter(mapped.readline, ""):
        if i == Line_I_want_to_jump:
            offsets = mapped.tell()
        i+=1

然后使用 f.seek(offsets) 移动到你需要的行

【讨论】:

这么好的答案。【参考方案11】:

我也遇到过同样的问题(需要从大文件的特定行中检索)。

当然,我每次都可以遍历文件中的所有记录并在计数器等于目标行时停止它,但是在您想要获得复数个特定行的情况下它不起作用。这导致主要问题得到解决 - 如何直接处理必要的文件位置。

我发现了下一个决定: 首先,我用每行的起始位置完成了字典(键是行号,值是前行的累积长度)。

t = open(file,’r’)
dict_pos = 

kolvo = 0
length = 0
for each in t:
    dict_pos[kolvo] = length
    length = length+len(each)
    kolvo = kolvo+1

最后,瞄准功能:

def give_line(line_number):
    t.seek(dict_pos.get(line_number))
    line = t.readline()
    return line

t.seek(line_number) - 执行文件修剪直到行开始的命令。 因此,如果您下一次提交 readline – 您将获得目标行。

使用这种方法,我节省了大量时间。

【讨论】:

【参考方案12】:

是什么生成了您要处理的文件?如果它在您的控制之下,您可以在附加文件时生成一个索引(哪一行在哪个位置。)。索引文件可以是固定的行大小(空格填充或 0 填充数字)并且肯定会更小。从而可以快速读取和处理。

您想要哪条线路? 计算索引文件中对应行号的字节偏移量(可能是因为索引文件的行大小是固定的)。 使用 seek 或其他任何方式直接跳转以从索引文件中获取行。 解析以获取实际文件对应行的字节偏移量。

【讨论】:

【参考方案13】:

这些行本身是否包含任何索引信息?如果每一行的内容类似于“&lt;line index&gt;:Data”,那么可以使用seek() 方法对文件进行二进制搜索,即使Data 的数量是可变的。你会寻找文件的中点,读取一行,检查它的索引是高于还是低于你想要的,等等。

否则,你能做的最好的就是readlines()。如果您不想读取全部 15MB,可以使用sizehint 参数至少将大量readline()s 替换为对readlines() 的少量调用。

【讨论】:

【参考方案14】:

由于不阅读就无法确定所有行的长度,因此您别无选择,只能遍历起始行之前的所有行。你所能做的就是让它看起来不错。如果文件真的很大,那么您可能需要使用基于生成器的方法:

from itertools import dropwhile

def iterate_from_line(f, start_from_line):
    return (l for i, l in dropwhile(lambda x: x[0] < start_from_line, enumerate(f)))

for line in iterate_from_line(open(filename, "r", 0), 141978):
    DoSomethingWithThisLine(line)

注意:基于这种方法,索引为零。

【讨论】:

【参考方案15】:

如果你不想读取内存中的整个文件..你可能需要想出一些格式而不是纯文本。

当然,这完全取决于您要执行的操作,以及您跳过文件的频率。

例如,如果您要跳转到同一文件中的行多次,并且您知道该文件在使用它时不会更改,您可以这样做: 首先,遍历整个文件,并记录一些关键行号的“搜索位置”(例如,曾经 1000 行), 然后如果你想要第 12005 行,跳到 12000 的位置(你已经记录了)然后读 5 行,你就会知道你在第 12005 行 等等

【讨论】:

【参考方案16】:

如果不读取文件至少一次,您就无法继续前进,因为您不知道换行符在哪里。你可以这样做:

# Read in the file once and build a list of line offsets
line_offset = []
offset = 0
for line in file:
    line_offset.append(offset)
    offset += len(line)
file.seek(0)

# Now, to skip to line n (with the first line being line 0), just do
file.seek(line_offset[n])

【讨论】:

+1,但请注意,这仅在他要跳到几行随机行时才有用!但如果他只跳到一条线,那就太浪费了 +1:另外,如果文件没有改变,行号索引可以被pickle和重用,进一步分摊扫描文件的初始成本。 好的,在我跳到那里之后,我将如何从这个位置开始逐行处理? 需要注意的一点(特别是在 Windows 上):小心以二进制模式打开文件,或者使用 offset=file.tell()。在 Windows 上的文本模式下,该行将比它在磁盘上的原始长度短一个字节(\r\n 替换为 \n) @photographer:使用 read() 或 readline(),它们从 seek 设置的当前位置开始。【参考方案17】:

如果您事先知道文件中的位置(而不是行号),则可以使用file.seek() 转到该位置。

编辑:您可以使用linecache.getline(filename, lineno) 函数,该函数将返回lineno 行的内容,但只有在将整个文件读入内存之后。如果您从文件中随机访问行(因为 python 本身可能想要打印回溯),但对于 15MB 文件来说不是很好。

【讨论】:

我绝对不会为此使用 linecache,因为它会在返回请求的行之前读取内存中的整个文件。 是的,听起来好得令人难以置信。我仍然希望有一个模块可以有效地做到这一点,但倾向于使用 file.seek() 方法。

以上是关于如何跳转到巨大文本文件中的特定行?的主要内容,如果未能解决你的问题,请参考以下文章

如何在循环中修改文本文件中的特定行?

如何从文本文件中用逗号分隔行中的特定行和值

如果if条件满足,如何跳转到for循环中的特定位置?

读取文件时Python中的UnicodeDecodeError,如何忽略错误并跳转到下一行?

如何按行条件将巨大的 csv 文件读入 R?

文本编辑器打开大(巨型,巨大,大)文本文件[关闭]