为何加载为字典的数据内存开销是磁盘中相同数据的两倍?
为什么Python加载JSON后内存开销是磁盘的两倍?
这事儿我之前处理超大JSON数据集的时候踩过一模一样的坑,明明磁盘上才60GB,加载到Python里直接飙到110GB+,其实核心原因是JSON文本格式和Python内存中对象的存储逻辑完全不在一个维度上,具体拆解下来有这么几个关键点:
1. Python对象的固有内存开销
Python里的每一个基础对象(dict、list、int、float甚至字符串)都不是“裸数据”,而是带有额外管理信息的结构体:
- 一个空的
dict在64位系统上就占约40字节(包含引用计数、类型指针、哈希表元数据等),空list也差不多是这个大小; - 哪怕是一个整数,小整数虽然有全局 intern 缓存,但如果是大量不重复的大整数,每个
int对象要占28字节(64位);浮点数更甚,每个float对象占24字节; - 嵌套结构的开销更是翻倍:比如一个嵌套的
dict,每一层都要额外占用哈希表的内存,而JSON文件里只是用{}和:来表示结构,没有这些额外的元数据。
对比下来,磁盘上的JSON是纯文本紧凑存储,比如一个整数"12345"只占5字节,加载成Python的int后直接膨胀到28字节,差距一下子就拉开了。
2. JSON文本的天然紧凑性
JSON作为文本格式,本身就比内存中的对象结构紧凑:
- 重复的键名在JSON里是重复的字符串,但加载到Python中,虽然字符串会被 intern 缓存,但如果是大量不同的长键名,每个键都要占用独立的内存空间;
- 数组和对象的结构在JSON里只用少量符号(
[]、{}、,)表示,而Python的list需要存储每个元素的指针(64位系统每个指针8字节),光是指针的开销就占了很大一部分。
举个例子:一个包含1000个整数的列表,JSON里的文本可能只占几千字节,但加载成Python的list后,光是1000个指针就占8000字节,再加上每个整数的28字节,总开销直接飙到36000字节,是磁盘大小的好几倍。
3. 内存对齐与碎片问题
操作系统和Python的内存管理器都是按固定大小的块来分配内存的(比如内存页),这就会导致内存碎片:
- 大量小对象分配后,会留下很多无法被利用的小内存块,整体内存使用率降低,看起来就像是“用了更多内存”;
- 比如你有一堆大小不一的嵌套
dict,Python为每个dict分配的内存块会比实际需要的略大,日积月累下来,额外的开销就很可观了。
4. 加载过程中的临时对象(峰值开销)
虽然你说的是加载完成后的内存占用,但加载过程中JSON库(比如标准库的json.load)会创建大量临时对象,这些对象虽然最后会被GC回收,但加载峰值时的内存占用可能比最终的内存占用还要高,不过这部分通常不会留存太久。
一些优化建议
如果想降低内存开销,可以试试这些方法:
- 换用二进制序列化格式:比如MessagePack、Parquet或者Feather,这些格式是二进制的,磁盘存储更紧凑,加载到内存后的对象开销也远低于JSON;
- 用Pandas加载:
pandas.read_json会把相同键的列转换成数组存储,避免了每个dict重复存储键名的开销,内存占用会大幅降低; - 用自定义类替代dict:比如用
@dataclass或者__slots__定义数据类,替代原生的dict,可以减少每个对象的内存开销(dict的哈希表开销很大,自定义类可以省去这部分); - 逐行加载(如果适用):如果你的JSON是每行一个对象的格式,可以用生成器逐行处理,不用一次性把所有数据载入内存,但这只适合不需要全量数据在内存的场景。
内容的提问来源于stack exchange,提问作者vaer-k
相关产品推荐
相关产品推荐

