Python 存储字符串时节省空间的方法


Posted in Python onApril 23, 2019

从 Python 3 开始,str 类型代表着 Unicode 字符串。取决于编码的类型,一个 Unicode 字符可能会占 4 个字节,这个有些时候有点浪费内存。

出于内存占用以及性能方面的考虑,Python 内部采用下面 3 种方式来存储 Unicode 字符:

  • 一个字符占一个字节(Latin-1 编码)
  • 一个字符占二个字节(UCS-2 编码)
  • 一个字符占四个字节(UCS-4 编码)

使用 Python 进行开发的时候,我们会觉得字符串的处理都很类似,很多时候根本不需要注意这些差别。可是,当碰到大量的字符处理的时候,这些细节就要特别注意了。

我们可以做一些小实验来体会下上面三种方式的差别。方法 sys.getsizeof 用来获取一个对象所占用的字节,这里我们会用到。

>>> import sys
>>> string = 'hello'
>>> sys.getsizeof(string)
54
>>> # 1-byte encoding
... sys.getsizeof(string + '!') - sys.getsizeof(string)
1
>>> # 2-byte encoding
... string2 = '你'
>>> sys.getsizeof(string2 + '好') - sys.getsizeof(string2)
2
>>> sys.getsizeof(string2)
76
>>> # 4-byte encoding
... string3 = ':snake:'
>>> sys.getsizeof(string3 + ':computer:') - sys.getsizeof(string3)
4
>>> sys.getsizeof(string3)
80

如上所示,当字符串的内容不同时,所采用的编码也会不同。需要注意的是,Python 中每个字符串都会另外占用 49-80 字节的空间,用于存储额外的一些信息,比如哈希、字符串长度、字符串字节数和字符串标识。这么一来,一个空字符串会占用 49 个字节,也就好理解了。

我们可以通过 cbytes 直接获取一个对象的编码类型:

import ctypes
class PyUnicodeObject(ctypes.Structure):
 # internal fields of the string object
 _fields_ = [("ob_refcnt", ctypes.c_long),
    ("ob_type", ctypes.c_void_p),
    ("length", ctypes.c_ssize_t),
    ("hash", ctypes.c_ssize_t),
    ("interned", ctypes.c_uint, 2),
    ("kind", ctypes.c_uint, 3),
    ("compact", ctypes.c_uint, 1),
    ("ascii", ctypes.c_uint, 1),
    ("ready", ctypes.c_uint, 1),
    # ...
    # ...
    ]
def get_string_kind(string):
 return PyUnicodeObject.from_address(id(string)).kind

然后测试

>>> get_string_kind('Hello')
1
>>> get_string_kind('你好')
2
>>> get_string_kind(':snake:')
4

如果一个字符串中的所有字符都能用 ASCII 表示,那么 Python 会使用 Latin-1 编码。简单说下,Latin-1 用于表示前 256 个 Unicode 字符。它能支持很多拉丁语言,比如英语、瑞典语、意大利语等。不过,如果是汉语、日语、西伯尔语等非拉丁语言,Latin-1 编码就行不通了。因为这些语言的文字的码位值(编码值)超过了 1 个字节的范围(0-255)。

>>> ord('a')
97
>>> ord('你')
20320
>>> ord('!')
33

大部分语言文字使用 2 个字节(UCS-2)来编码就已经足够了。4 个字节(UCS-4)的编码在保存特殊符号、emoji 表情或者少见的语言文字的时候会用到。

设想有一个 10GB 的 ASCII 文本文件,我们准备将其读到内存里面去。如果你插入一个 emoji 表情到文件中,文件占用空间将会达到 4 倍。如果你处理 NLP 问题较多的话,这种差别你应该能经常体会到。

Python 内部为什么不直接使用 UTF-8 编码

最常见的 Unicode 编码是 UTF-8,但是 Python 内部并没有使用它。

UTF-8 编码字符的时候,取决于字符的内容,占的空间在 1-4 个字节内发生变化。这是一种特别省空间的存储方式,但正因为这种变长的存储方式,导致字符串不能通过下标直接进行随机读取,只能遍历进行查找。比如,如果采用的是 UTF-8 编码的话,Python 获取 string[5] 只能一个一个字符的进行扫描,直至找到目标字符。如果是定长编码的话也就没有问题了,要用一个下标定位一个字符,只需要用下标乘以指定长度(1、2 或者 4)就能确定。

字符串驻留

Python 中的空字符串和 ASCII 字符都会使用到字符串驻留(string interning)技术。怎么理解?你就把这些字符(串)看作是单例的就行。也就是说,两个相同内容的字符串如果使用了驻留的技术,那么内存里面其实就只开辟了一个空间。

>>> a = 'hello'
>>> b = 'world'
>>> a[4],b[1]
('o', 'o')
>>> id(a[4]), id(b[1]), a[4] is b[1]
(4567926352, 4567926352, True)
>>> id('')
4545673904
>>> id('')
4545673904

正如你看到的那样,a 中的字符 o 和 b 中的字符 o 有着同样的内存地址。Python 中的字符串是不可修改的,所以提前为某些字符分配好位置便于后面使用也是可行的。

使用到字符串驻留的除了 ASCII 字符、空窜之外,字符长度不超过 20 的串也使用到了同样的技术,前提是这些串的内容在编译的时候就能确定。

这包括:

  • 方法名、类型
  • 变量名
  • 参数名
  • 常量(代码中定义的字符串)
  • 字典的键
  • 属性名

当你在交互式命令行中编写代码的时候,语句同样也会先被编译成字节码。所以说,交互式命令行中的短字符串也会被驻留。

>>> a = 'teststring'
>>> b = 'teststring'
>>> id(a), id(b), a is b
(4569487216, 4569487216, True)
>>> a = 'test'*5
>>> b = 'test'*5
>>> len(a), id(a), id(b), a is b
(20, 4569499232, 4569499232, True)
>>> a = 'test'*6
>>> b = 'test'*6
>>> len(a), id(a), id(b), a is b
(24, 4569479328, 4569479168, False)

因为必须是常量字符串会使用到驻留,所以下面的例子不能达到驻留的效果:

>>> open('test.txt','w').write('hello')
5
>>> open('test.txt','r').read()
'hello'
>>> a = open('test.txt','r').read()
>>> b = open('test.txt','r').read()
>>> id(a), id(b), a is b
(4384934576, 4384934688, False)
>>> len(a), id(a), id(b), a is b
(5, 4384934576, 4384934688, False)

字符串驻留技术,减少了大量的重复字符串的内存分配。Python 底层通过字典实现的这种技术,这些暂存的字符串作为字典的键。如果想要知道某个字符串是否已经驻留,使用字典的查找操作就能确定。

Python 的 unicode 对象的实现( https://github.com/python/cpython/blob/master/Objects/unicodeobject.c )大约有 16,000 行 C 代码,其中有很多小优化在本文中未提及。如果你想更多的了解 Python 中的 Unicode,推荐你去看一下字符串相关的 PEPs( https://www.python.org/dev/peps/ ),同时查看下 unicode 对象的源码。

总结

以上所述是小编给大家介绍的Python 存储字符串时节省空间的方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Python 相关文章推荐
python应用程序在windows下不出现cmd窗口的办法
May 29 Python
python实现数独游戏 java简单实现数独游戏
Mar 30 Python
python合并已经存在的sheet数据到新sheet的方法
Dec 11 Python
Python3+Appium实现多台移动设备操作的方法
Jul 05 Python
Python3+Appium安装使用教程
Jul 05 Python
Django ORM 常用字段与不常用字段汇总
Aug 09 Python
python实现最大优先队列
Aug 29 Python
Django配置MySQL数据库的完整步骤
Sep 07 Python
python利用dlib获取人脸的68个landmark
Nov 27 Python
pytorch实现mnist数据集的图像可视化及保存
Jan 14 Python
TensorFlow使用Graph的基本操作的实现
Apr 22 Python
浅谈keras中的Merge层(实现层的相加、相减、相乘实例)
May 23 Python
Django页面数据的缓存与使用的具体方法
Apr 23 #Python
Python切片操作去除字符串首尾的空格
Apr 22 #Python
详解python中的hashlib模块的使用
Apr 22 #Python
Python 中包/模块的 `import` 操作代码
Apr 22 #Python
python定时检测无响应进程并重启的实例代码
Apr 22 #Python
django query模块
Apr 20 #Python
不到20行代码用Python做一个智能聊天机器人
Apr 19 #Python
You might like
php实现的在线人员函数库
2008/04/09 PHP
php模板中出现空行解决方法
2011/03/08 PHP
php的urlencode()URL编码函数浅析
2011/08/09 PHP
php+mysql数据库查询实例
2015/01/21 PHP
分享PHP源码批量抓取远程网页图片并保存到本地的实现方法
2015/12/01 PHP
PHP的Yii框架中创建视图和渲染视图的方法详解
2016/03/29 PHP
Laravel框架实现redis集群的方法分析
2017/09/14 PHP
PHP defined()函数的使用图文详解
2019/07/20 PHP
List Installed Hot Fixes
2007/06/12 Javascript
jquery.ui.draggable中文文档
2009/11/24 Javascript
javascript 异步页面查询实现代码(asp.net)
2010/05/26 Javascript
基于JQuery的asp.net树实现代码
2010/11/30 Javascript
JS继承--原型链继承和类式继承
2013/04/08 Javascript
JavaScript DSL 流畅接口(使用链式调用)实例
2015/03/15 Javascript
JS简单实现DIV相对于浏览器固定位置不变的方法
2016/06/17 Javascript
微信+angularJS的SPA应用中用router进行页面跳转,jssdk校验失败问题解决
2016/09/09 Javascript
vue和webpack安装命令详解
2018/06/15 Javascript
vue实现文件上传功能
2018/08/13 Javascript
Vue实现本地购物车功能
2018/12/05 Javascript
微信小程序实现图片上传
2019/05/23 Javascript
[43:24]VG vs Serenity 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/20 DOTA
python数据结构之图的实现方法
2015/07/08 Python
python 多线程实现检测服务器在线情况
2015/11/25 Python
Windows下安装Scrapy
2018/10/17 Python
详解Python3 对象组合zip()和回退方式*zip
2019/05/15 Python
python rsync服务器之间文件夹同步脚本
2019/08/29 Python
Python在OpenCV里实现极坐标变换功能
2019/09/02 Python
如何使用python-opencv批量生成带噪点噪线的数字验证码
2020/12/21 Python
联想新加坡官方网站:Lenovo Singapore
2017/10/24 全球购物
医院护理人员的自我评价分享
2013/10/04 职场文书
医学院毕业生自荐信
2013/11/08 职场文书
婚纱摄影师求职信
2014/03/07 职场文书
班级年度安全计划书
2014/05/01 职场文书
2014年个人总结范文
2015/03/09 职场文书
学术会议通知
2015/04/15 职场文书
《绝招》教学反思
2016/02/20 职场文书