让我们说我在Python中有一个字符串:
>>> s = 'python'
>>> len(s)
6
现在我encode
这个字符串是这样的:
>>> b = s.encode('utf-8')
>>> b16 = s.encode('utf-16')
>>> b32 = s.encode('utf-32')
我从上面的操作得到的是一个字节数组 - 即b
,b16
和b32
只是字节数组(每个字节当然是8位长) )。
但我们编码字符串。那么这是什么意思?我们如何附加"编码"的概念?用原始数组的字节?
答案在于这些字节数组中的每一个都以特定方式生成。让我们来看看这些数组:
>>> [hex(x) for x in b]
['0x70', '0x79', '0x74', '0x68', '0x6f', '0x6e']
>>> len(b)
6
此数组表示对于每个字符,我们有一个字节(因为所有字符都低于127)。因此,我们可以说"编码"字符串到' utf-8'收集每个字符的相应代码点并将其放入数组中。如果代码点不能适合一个字节,则utf-8消耗两个字节。因此utf-8消耗尽可能少的字节数。
>>> [hex(x) for x in b16]
['0xff', '0xfe', '0x70', '0x0', '0x79', '0x0', '0x74', '0x0', '0x68', '0x0', '0x6f', '0x0', '0x6e', '0x0']
>>> len(b16)
14 # (2 + 6*2)
在这里,我们可以看到"编码为utf-16"首先将两个字节的BOM(FF FE
)放入bytes数组中,然后,对于每个字符,它将两个字节放入数组中。 (在我们的例子中,第二个字节始终为零)
>>> [hex(x) for x in b32]
['0xff', '0xfe', '0x0', '0x0', '0x70', '0x0', '0x0', '0x0', '0x79', '0x0', '0x0', '0x0', '0x74', '0x0', '0x0', '0x0', '0x68', '0x0', '0x0', '0x0', '0x6f', '0x0', '0x0', '0x0', '0x6e', '0x0', '0x0', '0x0']
>>> len(b32)
28 # (2+ 6*4 + 2)
在utf-32"中进行"编码的情况下,我们首先放入BOM,然后为每个字符放置四个字节,最后我们将两个零字节放入数组中。
因此,我们可以说"编码过程"为字符串中的每个字符收集1 2或4个字节(取决于编码名称)并在其前面添加更多字节以创建最终结果字节数组。
现在,我的问题:
b
,b16
和b32
的内存表示实际上是一个字节列表。字符串的内存表示是什么?究竟是什么存储在内存中的字符串?encode()
时,会收集每个字符对应的代码点(对应于编码名称的代码点)并放入一个或多个数组。当我们执行decode()
时会发生什么?答案 0 :(得分:18)
首先,UTF-32是一个4字节编码,所以它的BOM也是一个四字节序列:
>>> import codecs
>>> codecs.BOM_UTF32
b'\xff\xfe\x00\x00'
由于不同的计算机体系结构对字节顺序的处理方式不同(称为Endianess),因此BOM有两种变体,即小端和大端:
>>> codecs.BOM_UTF32_LE
b'\xff\xfe\x00\x00'
>>> codecs.BOM_UTF32_BE
b'\x00\x00\xfe\xff'
BOM的目的是将该订单传达给解码器;阅读BOM,如果它是大端或小端,你知道。因此,UTF-32字符串中的最后两个空字节是最后编码字符的一部分。
UTF-16 BOM因此类似,因为有两种变体:
>>> codecs.BOM_UTF16
b'\xff\xfe'
>>> codecs.BOM_UTF16_LE
b'\xff\xfe'
>>> codecs.BOM_UTF16_BE
b'\xfe\xff'
这取决于您的计算机体系结构默认使用哪一个。
UTF-8根本不需要BOM; UTF-8每个字符使用1个或多个字节(根据需要添加字节以编码更复杂的值),但这些字节的顺序在标准中定义。 Microsoft认为有必要引入UTF-8 BOM(因此其Notepad应用程序可以检测到UTF-8),但由于BOM的顺序从不变化,因此不建议使用它。
Python为unicode字符串存储的内容;在Python 3.3中实际发生了变化。在3.3之前,在C级内部,Python要么存储UTF16或UTF32字节组合,这取决于Python是否使用宽字符支持进行编译(参见How to find out if Python is compiled with UCS-2 or UCS-4?,UCS-2 本质上是 UTF-16和UCS-4是UTF-32)。因此,每个字符需要2或4个字节的内存。
从Python 3.3开始,内部表示使用表示字符串中所有字符所需的 minimal 字节数。对于纯ASCII和Latin1可编码文本,使用1个字节,因为使用了BMP 2个字节的其余部分,并且使用包含超过该4个字节的字符的文本。 Python根据需要在格式之间切换。因此,对于大多数情况,存储变得更加有效。有关详细信息,请参阅What's New in Python 3.3。
我强烈建议你阅读Unicode和Python:
答案 1 :(得分:5)
答案 2 :(得分:2)
我假设您正在使用Python 3(在Python 2中,“字符串”实际上是一个字节数组,这会导致Unicode痛苦。)
A(Unicode)字符串在概念上是一系列Unicode代码点,它们是对应于“字符”的抽象实体。你可以在Python repository.中看到实际的C ++实现。由于计算机没有代码点的固有概念,'encoding'指定了代码点和字节序列之间的部分双射。
编码被设置为可变宽度编码没有歧义 - 如果你看到一个字节,你总是知道它是否完成了当前的代码点,或者你是否需要读取另一个。从技术上讲,这被称为prefix-free.所以当你执行.decode()
时,Python会遍历字节数组,逐个构建一个编码字符并输出它们。
两个零字节是utf32 BOM的一部分:big-endian UTF32将0x0 0x0 0xff 0xfe
。