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


107

以下代码是否有替代方法:

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”,而后一半是一行?您为什么对此感到难过?
2009年

7
我认为标题具有误导性-至少可以说,TBH 15MB并不是真正的“巨大文本文件” ...
pms 2012年

Answers:


30

行缓存

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


164
我刚刚检查了该模块的源代码:整个文件都在内存中读取!因此,我绝对会排除此答案,以便快速访问文件中的给定行。
MiniQuark

MiniQuark,我尝试过,它确实有效,而且很快。我需要看看如果我同时用这种方式处理十几个文件会发生什么,找出系统死于什么时候。
user63503 2009年

5
操作系统的虚拟内存管理器可以提供很多帮助,因此,如果您不会产生很多页面错误,那么将大文件读入内存可能不会很慢:)相反,以“愚蠢的方式”进行操作并分配很多的存储速度可以非常快。我喜欢丹麦的FreeBSD开发人员Poul-Henning Kamp的文章: queue.acm.org/detail.cfm?id=1814327
Morten Jensen

13
尝试100G文件,很烂。我必须使用f.tell(),f.seek(),f.readline()
WHI

114

由于您不知道换行符在哪里,因此无法至少一次不读入文件就无法跳转。您可以执行以下操作:

# 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])

2
+1,但请注意,这仅在他要跳到几条随机行的情况下才有用!但是如果他只跳到一条直线上,那么这是浪费的
Hasen

3
+1:此外,如果文件没有更改,则可以腌制和重用行号索引,从而进一步分摊了扫描文件的初始成本。
S.Lott

好的,我跳到那里之后,如何从该位置开始逐行处理?
user63503 2009年

8
需要注意的一件事(尤其是在Windows上):请小心以二进制模式打开文件,或者使用offset = file.tell()。在Windows上的文本模式下,该行将比磁盘上的原始长度短一个字节(\ r \ n替换为\ n)
布莱恩

2
@photographer:使用read()或readline(),它们从seek设置的当前位置开始。
S.Lott

22

如果各行的长度不同,则实际上没有太多选择。可悲的是,您需要处理行结束符以知道何时前进到下一行。

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

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


6
8K是8192,为了安全起见,最好写8 << 10。:)
放松

您是否偶然知道在字节上指定了buffersize?什么是适当的格式?我可以写“ 8k”吗?还是应该是“ 8096”?
user63503,2009年

1
哈哈哈...一定是星期五...我显然不能做数学。缓冲区大小确实是一个表示字节的整数,所以写8192(不是8096 :-),而不是8
Jarret Hardie

我很高兴-希望能解决。在现代系统上,您可能会相当大地增加缓冲区大小。由于无法确定的原因,8k只是内存中的保留。
Jarret Hardie

我在这里进行了一些测试,并将其设置为-1(操作系统默认值,通常为8k,但通常很难说出),似乎速度差不多。就是说,部分原因可能是我正在虚拟服务器上进行测试。
奥斯卡·史密斯

12

我可能被大量的ram宠坏了,但是15 M并不庞大。readlines() 我通常用这种大小的文件读入内存。在那之后访问一条线很简单。


为什么我有点犹豫要读取整个文件-我可能正在运行这些进程中的几个,并且如果其中几个读取12个15MB的文件,那可能就不好了。但是我需要对其进行测试,以了解它是否可以正常工作。谢谢。
user63503 2009年

4
Hrm,如果是1GB的文件怎么办?
诺亚

@photographer:即使是“ 15”文件中的“几个”进程,在典型的现代计算机上也没有关系(当然,这完全取决于您对它们的处理方式)。
Jacob Gabrielson

雅各布,是的,我应该尝试。如果虚拟机未崩溃,则该进程在虚拟机上运行数周。不幸的是,上次它在6天后崩溃了。我需要从突然停止的地方继续。仍然需要弄清楚如何找到它的剩余位置。
user63503 2009年

@诺亚:但事实并非如此!你为什么不走得更远?如果文件128TB怎么办?许多操作系统无法支持它。为什么不解决问题呢?
SilentGhost

7

我很惊讶没有人提到伊丽丝

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

5

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

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)

注意:在这种方法中,索引为零。


4

如果您不想读取内存中的整个文件,则可能需要使用纯文本以外的其他格式。

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

例如,如果您要在同一个文件中多次跳转到第行,并且知道该文件在使用时不会更改,则可以执行以下操作:
首先,遍历整个文件,并记录“某些关键行号(例如,曾经有1000行)的“ seek-location”,
然后,如果您想要12005行,请跳到12000(已记录)的位置,然后阅读5行,您就会知道在12005行,依此类推


3

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

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


我绝对不会为此使用linecache,因为它会在返回请求的行之前读取内存中的整个文件。
MiniQuark

是的,听起来太好了,难以置信。我仍然希望有一个模块可以有效地执行此操作,但是倾向于使用file.seek()方法代替。
挪亚

3

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

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

3

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

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

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

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 –您将获得目标行。

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


3

您可以使用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)移至所需的行


2

这些行本身是否包含任何索引信息?如果每一行的内容都类似于“ <line index>:Data”,则该seek()方法可用于对文件进行二进制搜索,即使Data可变。您将寻找到文件的中点,读取一行,检查其索引是高于还是低于您想要的索引,等等。

否则,您能做的最好就是readlines()。如果您不想读取全部15MB的内存,则可以使用sizehint参数至少用readline()较少的调用替换很多readlines()


2

如果您要处理基于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不兼容。
Wizmann '16

这比用Python更快吗?
Shamoon,

可以得到多行吗?
Shamoon

1

这是一个使用'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)

0

没有一个答案特别令人满意,因此这里有一个小片段可以帮助您。

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许可提供了以上代码段。


-1

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

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

如果存在连续的空行,则此逻辑不起作用,fi.next()会一次跳过所有空行,否则会很好:)
Anvesh Yalamarthy

OP没有提到这些行的行具有非标准的换行符。在这种情况下,您必须使用至少一个if语句来分析每行,以获取部分换行符。
ksed
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.