python - 在 Python (SageMath 9.0) 中 - 1B 行上的文本文件 - 从特定行读取的最佳方式
问题描述
我在 Windows 10 操作系统上运行 SageMath 9.0
我在这个网站上阅读了几个类似的问题(和答案)。主要是从第 7 行读取的这一篇,以及关于优化的这一篇。但是我有一些具体的问题:我需要了解如何从特定的(可能很远的)行以最佳方式读取,以及我是否应该逐行读取,或者在我的情况下是否按块读取可能“更佳”。
我有一个 12Go 文本文件,由大约 10 亿行小行组成,全部由 ASCII 可打印字符组成。每行都有固定数量的字符。以下是实际的前 5 行:
J??????????
J???????C??
J???????E??
J??????_A??
J???????F??
...
对于上下文,此文件是 11 个顶点上的所有非同构图的列表,使用 graph6 格式编码。该文件由Brendan McKay计算并在其网页上提供。
我需要检查每个图表的某些属性。我可以使用发电机for G in graphs(11)
,但这可能会很长(至少在我的笔记本电脑上几天)。我想在文件中使用完整的数据库,以便能够从某个点停止并重新开始。
我当前的代码从头开始逐行读取文件,并在读取每一行后进行一些计算:
with open(filename,'r') as file:
while True:
# Get next line from file
line = file.readline()
# if line is empty, end of file is reached
if not line:
print("End of Database Reached")
break
G = Graph()
from_graph6(G,line.strip())
run_some_code(G)
为了能够停止代码,或者在崩溃的情况下保存进度,我在想:
- 每读取一百万行(左右),将进度保存在特定文件中
- 重新启动代码时,读取最后保存的值,而不是使用
line = file.readline()
,我会使用 itertool 选项,for line in islice(file, start_line, None)
.
所以我的新代码是
from itertools import islice
start_line = load('foo')
count = start_line
save_every_n_lines = 1000000
with open(filename,'r') as file:
for line in islice(file, start_line, None):
G = Graph()
from_graph6(G,line.strip())
run_some_code(G)
count +=1
if (count % save_every_n_lines )==0:
save(count,'foo')
该代码确实有效,但我想了解是否可以对其进行优化。我不喜欢我在循环中的if
陈述。for
- 这里是
itertools.islice()
不错的选择吗?该文档指出“如果 start 不为零,则跳过 iterable 中的元素,直到达到 start ”。由于“开始”可能非常大,鉴于我正在处理简单的文本文件,是否有更快的选择,以便直接“跳转”到开始行? - 知道文本文件是固定的,将实际文件拆分为 100 或 1000 个较小的文件并逐个读取它们会更优化吗?这将在我的循环中读取
if
语句。for
- 我还可以选择一次性读取行块,而不是逐行读取,然后处理图表列表。那会是一个不错的选择吗?
每行都有固定数量的字符。所以“跳跃”可能是可行的。
解决方案
假设每一行的大小相同,您可以使用内存映射文件按索引读取它,而无需使用 seek 和 tell。内存映射文件模拟 a bytearray
,您可以从数组中获取记录大小的切片以获得所需的数据。如果要暂停处理,只需将当前记录索引保存在数组中,以后可以使用该索引重新启动。
这个例子是在 linux 上的——在 windows 上打开 mmap 有点不同——但是在设置之后,访问应该是一样的。
import os
import mmap
# I think this is the record plus newline
LINE_SZ = 12
RECORD_SZ = LINE_SZ - 1
# generate test file
testdata = "testdata.txt"
with open(testdata, 'wb') as f:
for i in range(100):
f.write("R{: 10}\n".format(i).encode('ascii'))
f = open(testdata, 'rb')
data = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
# the i-th record is
i = 20
record = data[i*LINE_SZ:i*LINE_SZ+RECORD_SZ]
print("record 20", record)
# you can stick it in a function. this is a bit slower, but encapsulated
def get_record(mmapped_file, index):
return mmapped_file[i*LINE_SZ:i*LINE_SZ+RECORD_SZ]
print("get record 20", get_record(data, 11))
# to enumerate
def enum_records(mmapped_file, start, stop=None, step=1):
if stop is None:
stop = mmapped_file.size()/LINE_SZ
for pos in range(start*LINE_SZ, stop*LINE_SZ, step*LINE_SZ):
yield mmapped_file[pos:pos+RECORD_SZ]
print("enum 6 to 8", [record for record in enum_records(data,6,9)])
del data
f.close()
推荐阅读
- python - 如何从数据框中返回大于某个值的值?
- arrays - 用。。。来代替 ”?” 切片中的所有元素/使用 ? 创建一个字符串,用于切片中的尽可能多的元素
- go - 如何使用 godotenv 基于 build 命令在 env 文件之间切换?
- javascript - 使用 Object.assign 创建 html 元素时如何添加“data-*”属性
- c++ - 当我使用 get_current_dir_name() 时 JsonCpp 找不到文件
- sql-server - 将数据从服务器表迁移到另一个服务器表
- ibm-doors - 如何使用 DXL 从文件或流中获取修改日期?
- python - 整理来自多个 pytest 运行的结果
- ruby-on-rails - Heroku + rails + svelte 部署
- javascript - 如何使此面包屑模式生成器正常工作