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深入学习之上下文管理器
Aug 31 Python
详解django中自定义标签和过滤器
Jul 03 Python
Python 3.x 安装opencv+opencv_contrib的操作方法
Apr 02 Python
python修改list中所有元素类型的三种方法
Apr 09 Python
python实现字典嵌套列表取值
Dec 16 Python
python计算二维矩形IOU实例
Jan 18 Python
Tensorflow的梯度异步更新示例
Jan 23 Python
python使用梯度下降和牛顿法寻找Rosenbrock函数最小值实例
Apr 02 Python
Python实现捕获异常发生的文件和具体行数
Apr 25 Python
学python最电脑配置有要求么
Jul 05 Python
Python使用pycharm导入pymysql教程
Sep 16 Python
python中的random模块和相关函数详解
Apr 22 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页面编码的两种方法示例介绍
2014/03/03 PHP
thinkPHP中create方法与令牌验证实例浅析
2015/12/08 PHP
PHP+swoole+linux实现系统监控和性能优化操作示例
2019/04/15 PHP
php实现JWT验证的实例教程
2020/11/26 PHP
事件模型在各浏览器中存在差异
2010/10/20 Javascript
js/jquery获取文本框输入焦点的方法
2014/03/04 Javascript
node.js中的path.join方法使用说明
2014/12/08 Javascript
js带缩略图的图片轮播效果代码分享
2015/09/14 Javascript
详解JavaScript对Date对象的操作问题(生成一个倒数7天的数组)
2015/10/01 Javascript
Nodejs初级阶段之express
2015/11/23 NodeJs
JavaScript中匿名函数的用法及优缺点详解
2016/06/01 Javascript
js实现小窗口拖拽效果
2016/12/03 Javascript
微信小程序模板之分页滑动栏
2017/02/10 Javascript
高效的jQuery代码编写技巧总结
2017/02/22 Javascript
vue配置请求本地json数据的方法
2018/04/11 Javascript
javascript对HTML字符转义与反转义
2018/12/13 Javascript
使用webpack构建应用的方法步骤
2019/03/04 Javascript
node之本地服务器图片上传的方法示例
2019/03/26 Javascript
微信小程序select下拉框实现源码
2019/11/08 Javascript
electron+vue实现div contenteditable截图功能
2020/01/07 Javascript
JavaScript享元模式原理与用法实例详解
2020/03/09 Javascript
Element Input输入框的使用方法
2020/07/26 Javascript
[01:04:48]VGJ.S vs TNC Supermajor 败者组 BO3 第一场 6.6
2018/06/07 DOTA
python thread 并发且顺序运行示例
2009/04/09 Python
在Python操作时间和日期之asctime()方法的使用
2015/05/22 Python
关于python3.7安装matplotlib始终无法成功的问题的解决
2020/07/28 Python
如何用python免费看美剧
2020/08/11 Python
利用HTML5+CSS3实现3D转换效果实例详解
2017/05/02 HTML / CSS
美国知名运动产品零售商:Foot Locker
2016/07/23 全球购物
意大利在线药房:shop-farmacia.it
2019/03/12 全球购物
节电标语大全
2014/06/23 职场文书
早读课迟到检讨书
2014/09/25 职场文书
2015年护士医德医风自我评价
2015/03/03 职场文书
胡桃夹子观后感
2015/06/11 职场文书
python实现socket简单通信的示例代码
2021/04/13 Python
Python基础之元类详解
2021/04/29 Python