我有一个4000万行,3千兆字节的文本文件(可能无法适应内存),格式如下:
399.4540176 {Some other data}
404.498759292 {Some other data}
408.362737492 {Some other data}
412.832976111 {Some other data}
415.70665675 {Some other data}
419.586515381 {Some other data}
427.316825959 {Some other data}
.......
每行以数字开头,后跟一些其他数据。数字按排序顺序排列。我需要能够:
x
号和范围y
,找到号码在y
x
范围内的所有行。例如,如果x=20
和y=5
,我需要查找编号介于15
和25
之间的所有行。 在不必遍历整个文件的情况下,这样做的有效方法是什么?
答案 0 :(得分:5)
如果您不想提前为行长度生成数据库,可以尝试这样做:
import os
import sys
# Configuration, change these to suit your needs
maxRowOffset = 100 #increase this if some lines are being missed
fileName = 'longFile.txt'
x = 2000
y = 25
#seek to first character c before the current position
def seekTo(f,c):
while f.read(1) != c:
f.seek(-2,1)
def parseRow(row):
return (int(row.split(None,1)[0]),row)
minRow = x - y
maxRow = x + y
step = os.path.getsize(fileName)/2.
with open(fileName,'r') as f:
while True:
f.seek(int(step),1)
seekTo(f,'\n')
row = parseRow(f.readline())
if row[0] < minRow:
if minRow - row[0] < maxRowOffset:
with open('outputFile.txt','w') as fo:
for row in f:
row = parseRow(row)
if row[0] > maxRow:
sys.exit()
if row[0] >= minRow:
fo.write(row[1])
else:
step /= 2.
step = step * -1 if step < 0 else step
else:
step /= 2.
step = step * -1 if step > 0 else step
首先对文件执行二进制搜索,直到它接近(小于maxRowOffset
)要查找的行。然后它开始读取每一行,直到找到一个大于x-y
的行。该行以及它之后的每一行都被写入输出文件,直到找到大于x+y
的行,以及程序退出的那一行。
我在1,000,000行文件上对此进行了测试,并在0.05秒内运行。相比之下,读取每条花了3.8秒。
答案 1 :(得分:4)
您需要随机访问文本文件所不具备的行,除非这些行都填充到相同的长度。
一种解决方案是将表转储到具有两列的数据库(例如SQLite)中,一列用于数字,一列用于所有其他数据(假设数据保证适合任何允许的最大字符数)在数据库的单个列中)。然后索引数字列,你就可以了。
如果没有数据库,您可以读取文件一次并创建一个内存数据结构,其中的值对显示包含(数字,行偏移)。您可以通过添加每行的长度(包括行结束)来计算行偏移量。现在,您可以在数字上二进制搜索这些值对,并使用偏移量随机访问文件中的行。如果您需要稍后重复搜索,请选择内存中的结构并重新加载以供以后重复使用。
这会读取整个文件(您说您不想这样做),但只能执行一次以构建索引。之后,您可以根据需要对文件执行任意数量的请求,并且速度非常快。
请注意,第二个解决方案实际上是在文本文件上创建数据库索引。
在第二个解决方案中创建索引的粗略代码:
import Pickle
line_end_length = len('\n') # must be a better way to do this!
offset = 0
index = [] # probably a better structure to use than a list
f = open(filename)
for row in f:
nbr = float(row.split(' ')[0])
index.append([nbr, offset])
offset += len(row) + line_end_length
Pickle.dump(index, open('filename.idx', 'wb')) # saves it for future use
现在,您可以在列表上执行二进制搜索。可能有一个更好的数据结构用于累积索引值而不是列表,但我必须阅读各种集合类型。
答案 2 :(得分:2)
由于您希望匹配第一个字段,因此可以使用gawk
:
$ gawk '{if ($1 >= 15 && $1 <= 25) { print }; if ($1 > 25) { exit }}' your_file
修改:使用261,775,557行格式为2.5 GiB大的文件,搜索50,010,015
到50,010,025
行,我的Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz
需要27秒。对我来说听起来不错。
答案 3 :(得分:0)
为了找到以低于下限的数字开头的行,您必须逐行浏览文件,直到找到该行。没有其他办法,即必须为换行符读取和解析文件中的所有数据。
我们必须将此搜索运行到超出上限的第一行并停止。因此,它有助于文件已经排序。这段代码有望提供帮助:
with open(outpath) as outfile:
with open(inpath) as infile:
for line in infile:
t = float(line.split()[0])
if lower_limit <= t <= upper_limit:
outfile.write(line)
elif t > upper_limit:
break
我认为理论上没有其他选择。