Skip to main content
 首页 » 编程设计

python之如何在 Python 中随机播放磁盘上的文本文件

2025年05月04日15bonelee

我正在处理一个大约 12*10^6 行的文本文件,该文件存储在我的硬盘上。 文件结构为:

data|data|data|...|data\n 
data|data|data|...|data\n 
data|data|data|...|data\n 
... 
data|data|data|...|data\n 

没有标题,也没有唯一标识行的 id。

因为我想将它用于机器学习目的,所以我需要确保文本文件中没有可能影响随机学习的顺序。

通常我将此类文件上传到内存中,然后在将它们重写到磁盘之前将它们洗牌。不幸的是这次不可能,因为文件的大小,所以我必须直接在磁盘上管理洗牌(假设我没有磁盘空间问题)。关于如何使用 Python 有效地(以尽可能低的复杂性,即写入磁盘)管理此类任务有什么想法吗?

请您参考如下方法:

除了其中一个之外,所有这些想法都使用 O(N) 内存——但是如果您使用 array.array 或 numpy.ndarray 我们谈论的是 N*4字节,明显小于整个文件。 (为简单起见,我将使用普通列表;如果您需要帮助转换为更紧凑的类型,我也可以展示。)


使用临时数据库和索引列表:

with contextlib.closing(dbm.open('temp.db', 'n')) as db: 
    with open(path) as f: 
        for i, line in enumerate(f): 
            db[str(i)] = line 
    linecount = i 
    shuffled = random.shuffle(range(linecount)) 
    with open(path + '.shuffled', 'w') as f: 
        for i in shuffled: 
            f.write(db[str(i)]) 
os.remove('temp.db') 

这是 2N 单行磁盘操作和 2N 单 dbm-key 磁盘操作,应该是 2NlogN 单磁盘磁盘操作等效操作,因此总复杂度为 O(NlogN)。


如果你使用像 sqlite3 这样的关系数据库而不是 dbm,你甚至不需要索引列表,因为你可以这样做:

SELECT * FROM Lines ORDER BY RANDOM() 

这与上面的时间复杂度相同,空间复杂度是 O(1) 而不是 O(N)——理论上。实际上,您需要一个 RDBMS,它可以一次从 100M 的行集中为您提供一行,而无需在任何一侧存储这 100M。


另一种选择,不使用临时数据库——理论上 O(N**2),但实际上如果你碰巧有足够的内存让行缓存有用,可能会更快:

with open(path) as f: 
    linecount = sum(1 for _ in f) 
shuffled = random.shuffle(range(linecount)) 
with open(path + '.shuffled', 'w') as f: 
    for i in shuffled: 
        f.write(linecache.getline(path, i)) 

最后,通过将索引列表的大小加倍,我们可以消除临时磁盘存储。但在实践中,这可能会慢很多,因为您正在执行更多的随机访问读取,而驱动器几乎不擅长这些读取。

with open(path) as f: 
    linestarts = [f.tell() for line in f] 
    lineranges = zip(linestarts, linestarts[1:] + [f.tell()]) 
    shuffled = random.shuffle(lineranges) 
    with open(path + '.shuffled', 'w') as f1: 
        for start, stop in shuffled: 
            f.seek(start) 
            f1.write(f.read(stop-start))