跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Python算法

Python 列表内存存储本质:差异原因与优化建议

Python 列表内存占用远超理论值,核心在于对象模型与指针机制。小整数缓存和字符串驻留能复用相同元素,大幅降低开销;不同元素则需独立分配元数据。理解对象复用原理,结合 array、NumPy 等工具,可有效优化大数据场景下的内存效率。

DebugKing发布于 2026/3/16更新于 2026/7/532 浏览
Python 列表内存存储本质:差异原因与优化建议

在 Python 中处理大量字符串或数字时,你可能会遇到意想不到的内存占用问题。比如需要存储一百万个短字符串或数字,按每个字符串平均 10 字节、每个 64 位整数 8 个字节计算,理论上只需约 8 到 10MB 内存,但实际用 Python 列表存储时,内存使用可能会飙升到几十 MB。这背后的原因是什么?又该如何优化?

问题引入:列表存储的内存'膨胀'

先看一段简单的代码,用普通列表存储一百万个短字符串、相同的短字符串、整数、相同的整数:

str_list = [f"item_{i}" for i in range(1000000)]
same_item_str_list = [f"item" for i in range(1000000)]
num_list = [i for i in range(1000000)]
same_item_num_list = [0 for i in range(1000000)]

直觉上,每个字符串'item_xxx'大约 8-10 字节,每个整数 8 个字节,一百万条数据应该在 8 到 10MB 左右。但实际内存使用如何呢,我们用 pympler 来精确测量。

先安装 pympler:

uv add pympler 

修改代码,增加测量内存占用情况的打印:

from pympler import asizeof

str_list = [f"item_{i}" for i in range(1000000)]
same_item_str_list = [f"item" for i in range(1000000)]
num_list = [i for i in range(1000000)]
same_item_num_list = [0 for i in range(1000000)]

print(f"str_list 列表内存:{asizeof.asizeof(str_list)/1024/1024:.2f} MB")
print(f"same_item_str_list 列表内存:{asizeof.asizeof(same_item_str_list)/1024/1024:.2f} MB")
print(f"num_list 列表内存:{asizeof.asizeof(num_list)/1024/1024:.2f} MB")
print(f"same_item_num_list 列表内存:{asizeof.asizeof(same_item_num_list)/1024/1024:.2f} MB")

再次运行,得到的内存报告大致如下(具体数值因环境略有差异):

str_list 列表内存:61.46 MB
same_item_str_list 列表内存:8.06 MB
num_list 列表内存:38.57 MB
same_item_num_list 列表内存:8.06 MB

可以看到,四个列表的内存占用差异巨大:存储不同字符串的 str_list 占用 61.46MB,存储不同整数的 num_list 占用 38.57MB,而存储相同字符串和相同数字的列表都只占用约 8MB 内存。为什么同样是存储一百万条数据,内存占用会相差这么大呢?这需要先从数据的理论存储与实际存储差异说起。

理论存储与实际存储的差异

我们常说'每个 64 位整数占用 8 字节''每个字符占用 1 字节',这是硬件层面的理论存储需求,但在 Python 中,由于对象模型的设计,实际存储开销会远高于理论值。

64 位整数的存储差异

  • 理论值(8 字节):指在硬件和底层编程语言(如 C 语言)中,存储一个 64 位二进制数字所需的最小空间,仅包含数值本身,没有额外信息。
  • Python 实际值(28 字节以上):Python 中的整数是对象,其结构 PyIntObject(在 CPython 源码中实际名为 PyLongObject,整数对象统一使用长整型结构)包含:
    • 引用计数(8 字节):跟踪对象被引用的次数,用于垃圾回收
    • 类型指针(8 字节):标识该对象是整数类型(指向 PyLong_Type)
    • 长度字段(8 字节):记录整数占用的位数组长度(对于小整数固定为 1)
    • 数值数据(4 字节起):存储实际的整数数值(以位数组形式存储,小整数至少占用 4 字节对齐空间)

这些结构字段总和为 8+8+8+4=28 字节,因此即使是最小的整数,在 Python 中也需要 28 字节内存,是理论值的 3.5 倍。

短字符串的存储差异

  • 理论值(字符数×1 字节):在 ASCII 编码中,每个字符占用 1 字节,一个 8 字符的字符串理论上只需 8 字节。
  • Python 实际值(50 字节左右):Python 的字符串对象 PyUnicodeObject 包含:
    • 引用计数(8 字节)和类型指针(8 字节):基础对象元数据
    • 字符串长度(8 字节):记录字符数量
    • 哈希值(8 字节):用于快速比较和字典查找
    • 编码标志位(4 字节):记录字符串使用的编码格式(如 ASCII、UTF-8 等)
    • 字符数据及内存对齐(8 字符×1 字节 + 4 字节对齐填充):实际字符存储需要按 8 字节对齐,8 个字符本需 8 字节,但为满足内存对齐要求会填充 4 字节
    • 额外内存开销:Python 内存分配器会为小对象添加 4-8 字节的管理信息

以 8 字符的'item'字符串为例,元数据(36 字节)+ 字符数据(8 字节)+ 对齐填充(4 字节)+ 分配器开销(2 字节)≈50 字节,因此实际内存是理论值的 6 倍多。这种理论与实际的差异,是导致列表内存'膨胀'的基础原因。当存储大量元素时,每个元素的额外开销会累积成巨大的内存差异。

列表的内存存储本质

了解了整数和字符串的理论存储和实际存储差异,我们就可以开始学习列表的内存存储了。Python 列表本质上是指针数组,它存储的不是元素本身,而是元素对象在内存中的地址(指针)。在 64 位系统中,每个指针固定占用 8 字节,因此:

  • 无论列表中的元素是什么类型,一个包含 100 万个元素的列表,其指针数组本身的内存固定为 8MB(1000000×8 字节)。
  • 列表的总内存 = 指针数组内存 + 所有元素对象的内存总和。

这就解释了为什么 same_item_str_list 和 same_item_num_list 的内存都在 8MB 左右 —— 它们的指针数组占用 8MB,所有指针指向相同的内存地址,因为元素对象只有一个,所以元素对象的内存几乎可以忽略不计。而 str_list 和 num_list 内存飙升的原因,正是元素对象的内存开销很大。

相同元素列表内存少的核心原因:对象复用

当列表中的元素完全相同时(如 same_item_str_list 全是'item',same_item_num_list 全是 0),Python 会复用同一个对象,避免重复创建,从而大幅减少内存开销。

小整数的缓存复用机制

Python 对小整数(通常是 -5 到 256 之间) 采用预创建和缓存机制:这些整数在 Python 启动时就被提前创建,并存入全局缓存池,后续使用时直接复用,不会重复分配内存。

  • 在 same_item_num_list = [0 for i in range(1000000)] 中,所有元素都是 0,而 0 属于小整数,会被全局缓存复用。
  • 整个列表中,所有指针都指向同一个 0 对象,因此元素对象的内存只需存储1 个 0 的内存(约 28 字节)。
  • 总内存 = 指针数组(8MB)+1 个 0 对象内存(可忽略)≈8MB(与实测的 8.06MB 一致)。

这种机制极大提高了小整数的使用效率,尤其在循环计数、状态标记等场景中,避免了频繁创建和销毁整数对象的开销。

字符串的驻留(Intern)机制

Python 会对短字符串、标识符类字符串进行'驻留'(Intern)处理:相同的字符串会被存储在全局字符串池中,后续使用时直接复用,不会重复创建新对象。

  • 在 same_item_str_list = [f"item" for i in range(1000000)] 中,所有元素都是'item',这是一个短字符串且符合标识符规则(字母组成),会被自动驻留。
  • 整个列表中,所有指针都指向同一个'item'对象,元素对象的内存只需存储1 个'item'的内存(约 50 字节)。
  • 总内存 = 指针数组(8MB)+1 个'item'对象内存(可忽略)≈8MB(与实测的 8.06MB 一致)。

字符串驻留机制主要用于优化程序中频繁出现的相同字符串,如变量名、关键字、常量字符串等,减少内存浪费和字符串比较的时间开销。

不同元素列表内存高的原因:对象重复创建

当列表中的元素不同时(如 str_list 和 num_list),每个元素都是独立的新对象,需要为每个元素分配单独的内存,导致总内存剧增。

不同整数的内存开销

num_list = [i for i in range(1000000)] 中,元素是 0 到 999999:

  • 其中 0 到 256 是小整数,会被缓存复用,但 257 到 999999 是大整数,每个大整数都是独立的新对象。
  • 每个 Python 整数对象(尤其是大整数)的内存开销约 28 字节(包含引用计数、类型指针等元数据)。
  • 总内存 = 指针数组(8MB)+ 约 999744 个大整数对象内存(999744×28 字节≈27MB)≈35MB(与实测的 38.57MB 接近,差异来自小整数缓存和内存对齐)。

大整数没有缓存机制,每个大整数都需要单独分配内存,这就是 num_list 内存比相同元素数字列表高的原因。

不同字符串的内存开销

str_list = [f"item_{i}" for i in range(1000000)] 中,每个元素是不同的字符串('item_0' 到'item_999999'):

  • 这些字符串都是动态生成的不同内容,不会被驻留复用,每个都是独立的新字符串对象。
  • 每个短字符串对象的内存开销约 50-60 字节(包含元数据和字符数据)。
  • 总内存 = 指针数组(8MB)+100 万个独立字符串对象内存(1000000×50 字节≈48MB)≈56MB(与实测的 61.46MB 接近,差异来自字符串长度不同和元数据开销)。

动态生成的不同字符串无法被驻留机制复用,每个字符串都需要单独存储元数据和字符数据,导致内存开销远高于相同元素的字符串列表。

内存占用对比分析

列表类型指针数组内存(固定)元素对象内存(变量)总内存内存差异原因
same_item_num_list8MB28 字节(1 个 0 对象)8.06MB小整数缓存复用,元素内存可忽略
num_list8MB≈27MB(约 99 万个大整数)38.57MB大整数无缓存,每个都是新对象
same_item_str_list8MB50 字节(1 个'item'对象)8.06MB字符串驻留复用,元素内存可忽略
str_list8MB≈48MB(100 万个不同字符串)61.46MB动态字符串无驻留,每个都是新对象

优化建议:利用对象复用减少内存开销

了解了 Python 的对象复用机制后,我们可以采取以下策略优化列表内存占用:

  1. 复用小整数和短字符串:在需要存储大量重复元素的场景中,尽量使用小整数(-5 到 256)和可驻留的短字符串,避免动态生成不同的元素。
  2. 使用数据结构优化重复元素存储:对于包含大量重复元素的列表,可使用 array 模块或 Pandas 的 category 类型,这些结构会自动复用重复元素,减少内存开销。
  3. 避免无意义的对象创建:在循环中避免重复创建相同的对象,例如将 [f"abcdefghijklmnopqrstuvwxyz" for i in range(1000000)] 改为 item = "abcdefghijklmnopqrstuvwxyz"; [item for i in range(1000000)],确保元素对象只创建一次。
  4. 针对大整数和长字符串的优化:对于大量大整数,可考虑使用 NumPy 数组存储;对于大量字符串,可使用 Pandas 的 StringDtype 或 category 类型,利用其内置的重复元素压缩机制。

总结

Python 列表的内存占用差异主要来自元素对象的复用情况:相同元素的列表通过小整数缓存和字符串驻留机制复用对象,内存开销主要来自指针数组;而不同元素的列表需要为每个元素创建独立对象,每个对象的元数据开销累积导致内存飙升。

在实际开发中,当需要存储大量数据时,应充分利用 Python 的对象复用机制,选择合适的数据结构,避免无意义的对象重复创建。通过合理设计数据存储方式,既能减少内存占用,也能提高程序运行效率。

目录

  1. 问题引入:列表存储的内存“膨胀”
  2. 理论存储与实际存储的差异
  3. 64 位整数的存储差异
  4. 短字符串的存储差异
  5. 列表的内存存储本质
  6. 相同元素列表内存少的核心原因:对象复用
  7. 小整数的缓存复用机制
  8. 字符串的驻留(Intern)机制
  9. 不同元素列表内存高的原因:对象重复创建
  10. 不同整数的内存开销
  11. 不同字符串的内存开销
  12. 内存占用对比分析
  13. 优化建议:利用对象复用减少内存开销
  14. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • VSCode 本地运行 DeepSeek 模型教程
  • Windows 系统 Claude Code Git Bash 依赖修复方案
  • 案例教学:使用 AI 模型解决一道典型的动态规划题
  • 数据分析全流程实战:Python与MySQL数据处理及可视化对比
  • JavaScript 运算符与流程控制详解
  • AI 工具泛滥时代,为什么单纯掌握工具不再值钱?
  • 前端函数防抖详解
  • Alice 的安全旅行:路径节点限制下的最大安全度
  • ComfyUI Photoshop 插件配置指南:实现 AI 绘画工作流
  • Flutter serial 库在鸿蒙系统的适配指南与串口通信实战
  • FPGA 图像处理加速:核心算法解析
  • 基于龙卷风优化算法的多无人机协同路径规划与 Matlab 实现
  • 递归算法专题:汉诺塔、链表操作与快速幂
  • 2023 Python 考试题库(考生回忆版)
  • TeamSpeak3 音乐机器人搭建指南:语音聊天室音频播放实现
  • 基于 Nexent 知识库与 MCP 生态打造智能烹饪顾问实战
  • 大模型学习路径与核心技术指南
  • ZYNQ PS-PL 架构解析:ARM 与 FPGA 融合设计及通信机制
  • 详解 RabbitMQ 高级特性之死信队列
  • AIGC 个性化与定制化内容生成:技术原理与应用实践

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online