python 中字符串默认的是 Unicode 编码,数字字符、英文字符和中文字符在 Unicode 编码中不是都是 2 字节么?
为什么我用 getsizeof()函数来看字符串的内存大小,发现 1 个汉字字符是 2 字节没错,但 1 个数字字符却只有 1 字节?
我用 utf-8 编码检验了一下,没发现问题。1 个数字字符 1 字节,一个汉字 3 字节。
print('Unicode_123 =', sys.getsizeof('123'))
print('Unicode_12 =', sys.getsizeof('12'))
# Unicode_123-Unicode_12 可得字符串默认编码下 1 个数字字符所占内存
print('Unicode_一二三 =', sys.getsizeof('一二三'))
print('Unicode_一二 =', sys.getsizeof('一二'))
# Unicode_一二三-Unicode_一二可得字符串默认编码下 1 个汉字字符所占内存
print('Utf-8_123 =', sys.getsizeof('123'.encode('utf-8')))
print('Utf-8_12 =', sys.getsizeof('12'.encode('utf-8')))
# Utf-8_123-Utf-8_12 可得 utf-8 编码下 1 个数字字符所占内存
print('Utf-8_一二三 =', sys.getsizeof('一二三'.encode('utf-8')))
print('Utf-8_一二 =', sys.getsizeof('一二'.encode('utf-8')))
# Utf-8_一二三-Utf-8_一二可得 utf-8 编码下 1 个汉字字符所占内存
显示结果如下:
Unicode_123 = 52
Unicode_12 = 51
Unicode_一二三 = 80
Unicode_一二 = 78
Utf-8_123 = 36
Utf-8_12 = 35
Utf-8_一二三 = 42
Utf-8_一二 = 39
但如果字符串是数字或英文字母混合汉字时,新增 1 个数字字符是增加 2 字节
print('Unicode_一 =', sys.getsizeof('一'))
print('Unicode_1 一 =', sys.getsizeof('1 一'))
print('Unicode_a 一 =', sys.getsizeof('a 一'))
print('Unicode_一二 =', sys.getsizeof('一二'))
显示结果如下:
Unicode_一 = 76
Unicode_1 一 = 78
Unicode_a 一 = 78
Unicode_一二 = 78
这种情况是为什么呢?
1
justou 2020-02-03 22:20:58 +08:00
'1 一' 'a 一' 是不是多插入了一个空格?
|
2
peterliu502 OP @justou 好像还真是……
|
3
peterliu502 OP @justou 但还是没明白为何 ASCII 字符是 1 字节
|
4
justou 2020-02-04 14:46:31 +08:00
Unicode 字符串即 py3 的 str 底层是一个 Py_UNICODE 的数组,其实就是一个 wchar_t 数组( wchar_t 大小平台相关,或 16bit 或 32bit ),str 就是一个对底层 wchar_t 数组的封装。
按照你的例子,str 增加一个 ascii 字符,大小增加 2 字节的话,那么底层的 wchar_t 大小应该是 16bit ( 2 字节) https://docs.python.org/3/c-api/unicode.html#unicode-type |
5
peterliu502 OP @justou str 增加一个 ascii 字符是 1 字节,非 ascii 字符才是 2 字节
|
6
chenstack 2020-02-05 15:05:35 +08:00
Python3 字符串 encode 默认编码是 utf-8,中文一般占 3 字节,ascii 字符是 1 字节
下面例子是计算 bytes 的大小 >>> print('Utf-8_一二三 =', sys.getsizeof('一二三'.encode('utf-8'))) >>> print('Utf-8_一二 =', sys.getsizeof('一二'.encode('utf-8'))) Utf-8_一二三 = 42 Utf-8_一二 = 39 相差 3 下面例子是计算 str 的大小,每个字符都是 2 字节,也就是 @justou #4 说的 >>> print('Unicode_a 一 =', sys.getsizeof('a 一')) >>> print('Unicode_一二 =', sys.getsizeof('一二')) Unicode_a 一 = 78 Unicode_一二 = 78 |
7
justou 2020-02-05 15:35:28 +08:00
我上面的回复不全面,可以用以下函数来探索下:
from sys import getsizeof def probe_size_increment(init_str="", code_points=range(2, 300)): for i in code_points: s1 = init_str + ''.join(chr(n) for n in range(i)) s2 = init_str + ''.join(chr(n) for n in range(i+1)) print(f"{i}~{i+1} {getsizeof(s2) - getsizeof(s1)}") 1. probe_size_increment(init_str=""); 可以用一个字节来表示的码点最大为 255,可以看到,在这之前都是 1 的增长,除了 255~256 分配了一定空间,后面都是 2 的增长。 2. probe_size_increment(init_str="", code_points=range(2**16 - 10, 2**16 - 1));都是 2 的增长。 3. probe_size_increment(init_str="", code_points=range(2**16 + 10, 2**16 + 20)); 都是 4 的增长。 首先意识到 str 下面管理的是一个单一类型的 c 数组,可以有以下推论:当所有字符都可以用 1 字节表示时,这个 c 数组是 Py_UCS1[], 所以在 255 之前增长都是 1 ;同理,2 字节增长的是 Py_UCS2[],4 字节增长的是 Py_UCS4[] 找了下源码,应该是这个位置 https://github.com/python/cpython/blob/3.8/Objects/unicodeobject.c#L2322 跟我们的推论一致 Python 的一些实现用了很多优化手段,本身就是 C 语言搭建起来的一个框架。想探究其原理建议直接看 C 代码吧。之前有人发过解读 Python 源码的帖子,你找找。 |
8
peterliu502 OP @justou 谢谢,你这个答案我觉得算是解答了我的这个疑问
|