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

以下代码是否有其他选择:

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)与行的未知,但不同的长度,并需要跳到一个特定的行,我提前知道哪个数字?当我知道我至少可以忽略文件的前半部分时,逐个处理它们让我感觉很糟糕。寻找更优雅的解决方案,如果有的话。

195172 次浏览

Linecache :

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

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

编辑 : 您可以使用 Getline (文件名,lineno)函数,它将返回行 lineno 的内容,但是只能在将整个文件读入内存之后。如果您从文件中随机访问行(Python 本身可能希望打印一个回溯) ,那么这样做是好的,但是对于15MB 的文件就不好了。

我可能被大量的公羊宠坏了,但是15M 并不大。用 readlines()读入内存是我通常用这种大小的文件所做的事情。在此之后访问一行是微不足道的。

您不能至少一次不读取文件就跳转到前面,因为您不知道换行符在哪里。你可以这样做:

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

如果行的长度不同,你就没有那么多选择... ... 很遗憾,你需要处理行尾字符,以便知道你什么时候进入下一行。

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

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

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

当然,这完全取决于您想要做什么,以及您跳过文件的频率。

例如,如果要跳转到同一个文件中的 很多次行,并且知道使用该文件时该文件不会发生更改,那么可以这样做:
首先,通过整个文件,并记录一些关键行号的“寻找位置”(比如,每1000行) ,
然后,如果您想要行12005,那么跳到12000的位置(您已经记录了该位置) ,然后读5行,您就会知道您在行12005中 诸如此类

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

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)

注意: 在这种方法中,索引是从零开始的。

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

Otherwise, the best you can do is just readlines(). If you don't want to read all 15MB, you can use the sizehint argument to at least replace a lot of readline()s with a smaller number of calls to readlines().

下面是一个使用 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)

What generates the file you want to process? If it is something under your control, you could generate an index (which line is at which position.) at the time the file is appended to. The index file can be of fixed line size (space padded or 0 padded numbers) and will definitely be smaller. And thus can be read and processed qucikly.

  • 你想要哪条线。
  • 计算索引文件中相应行号的字节偏移量(可能是因为索引文件的行大小是常数)。
  • Use seek or whatever to directly jump to get the line from index file.
  • Parse to get byte offset for corresponding line of actual file.

我有同样的问题(需要从巨大的文件特定行检索)。

当然,我可以每次运行文件中的所有记录,并停止它时,计数器将等于目标行,但它不能有效地工作在一种情况下,您希望获得特定行的复数数量。这导致了主要问题的解决-如何处理直接到必要的文件位置。

我发现了下一个决定: Firstly I completed dictionary with start position of each line (key is line number, and value – cumulated length of previous lines).

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

Find (line _ number)-执行文件剪枝直到行开始的命令。 因此,如果您下一次提交 readline-您将获得您的目标行。

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

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

example:

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.find (偏移量)移动到您需要的行

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

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

如果你正在处理一个 文本文件 & 基于 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)

I am suprised no one mentioned 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

没有一个答案是特别令人满意的,所以这里有一个小片段可以提供帮助。

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 许可下提供上面的代码片段。

@ 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}')