使用Python mmap读取Wikidata压缩JSON Dump文件遇错求助
问题分析与解决
两个错误的根源
Unicode解码错误:直接对
bz2.open返回的文件对象做mmap是错误的。bz2.open的fileno()对应的是压缩后的原始文件数据流,而非解压后的内容。mmap读取的是压缩字节,而json.loads会尝试把这些压缩字节当成UTF-8文本解码,自然会出现解码错误——这根本不是正常的JSON文本字节。循环无法终止:同样因为
mmap映射的是压缩文件的全部内容,压缩数据的末尾不会出现空字节串b"",所以iter(mm.readline, b"")永远触发不了终止条件,循环会一直运行。
而不用mmap时能正常读取,是因为bz2.open的readline会自动逐行解压数据,返回正确的JSON字节,但这种流式解压速度确实较慢。
正确的实现方式
核心思路:先把bz2压缩文件解压到一个可随机访问的临时文件,再对这个临时文件做mmap——这样既利用了mmap的快速读取特性,又能拿到正确的解压后数据。
基础实现代码
import bz2 import mmap import json import tempfile def read_file(filename): # 创建临时文件存储解压后的内容 with tempfile.NamedTemporaryFile(mode='wb+', delete=False) as temp_f: # 先把bz2文件全部解压到临时文件 with bz2.open(filename, 'rb') as f: temp_f.write(f.read()) # 重置临时文件指针到开头 temp_f.seek(0) # 对临时文件做mmap with mmap.mmap(temp_f.fileno(), 0, access=mmap.ACCESS_READ) as mm: for line in iter(mm.readline, b""): line = line.rstrip(b",\n") if line in [b"[", b"]"]: continue yield json.loads(line)
优化版(减少磁盘占用)
如果担心临时文件占用过多磁盘空间,可以用SpooledTemporaryFile,当数据量超过阈值时才写入磁盘,否则存在内存中:
import bz2 import mmap import json import tempfile def read_file(filename): # max_size设置为100MB,可根据内存情况调整 with tempfile.SpooledTemporaryFile(mode='wb+', max_size=1024*1024*100) as temp_f: with bz2.open(filename, 'rb') as f: temp_f.write(f.read()) temp_f.seek(0) with mmap.mmap(temp_f.fileno(), 0, access=mmap.ACCESS_READ) as mm: for line in iter(mm.readline, b""): line = line.rstrip(b",\n") if line in [b"[", b"]"]: continue yield json.loads(line)
补充说明
- 为什么不能直接对
bz2.open的文件对象做mmap?因为bz2.open返回的是流式文件对象,不支持随机访问(seek操作无效),而mmap要求文件对象必须可随机访问,且映射的是原始磁盘文件的字节,不是解压后的流数据。 - 对于Wikidata这类超大dump文件,临时文件是最稳妥的方案——毕竟内存可能无法容纳整个解压后的文件。
内容的提问来源于stack exchange,提问作者sl_
相关产品推荐
相关产品推荐

