utf-16 编码字符串是否需要 [0xff, 0xfe] 前缀?

Is the [0xff, 0xfe] prefix required on utf-16 encoded strings?

重写问题!

我正在使用供应商的设备,该设备需要 "unicode encoding" 个字符串,其中每个字符以两个字节表示。我的字符串将始终基于 ASCII,因此我认为这是将我的字符串转换为供应商字符串的方法:

>>> b1 = 'abc'.encode('utf-16')

但是检查结果,我发现字节数组中有一个前导 [0xff, 0xfe]:

>>> [hex(b) for b in b1]
['0xff', '0xfe', '0x61', '0x0', '0x62', '0x0', '0x63', '0x0']

由于供应商的设备不需要 [0xff, 0xfe],我可以将其剥离...

>>> b2 = 'abc'.encode('utf-16')[2:]
>>> [hex(b) for b in b2]
['0x61', '0x0', '0x62', '0x0', '0x63', '0x0']

...这就是我想要的。

但真正让我感到惊讶的是,我可以解码 b1 和 b2,并且它们都可以重构为原始字符串:

>>> b1.decode('utf-16') == b2.decode('utf-16')
True

所以我的两个相互交织的问题:

这是文档使用的byte order mark. It's a prefix to a UTF document that indicates what endianness。它通过按字节顺序对代码点 0xFEFF 进行编码来实现这一点 - 在这种情况下,是小端(低位字节优先)。任何试图以相反的方式读取它的东西,在大端(首先是更重要的字节),都会将第一个字符读取为 0xFFFE,这是一个代码点,它不是一个有效的字符,通知 reader 它需要为文档的其余部分出错或切换字节顺序。

是字节顺序标记,a.k.a。 BOM:参见 https://en.wikipedia.org/wiki/UTF-16(查看副标题字节顺序编码方案)。 它的目的是让解码器检测编码是little-endian还是big-endian.

是以UTF-16编码的Unicode字节顺序标记。其目的是将字节顺序传达给 reader 期望使用 Unicode 字符编码编码的文本。

如果 reader 知道或知道字节顺序,则可以省略它。

'abc'.encode('utf-16-le')

答案,尤其是来自 usr2564301 的评论很有帮助:0xff 0xfe 前缀是 "Byte Order Marker",它带有字节序信息和字节串。如果你知道你想要哪种字节序,你可以指定 utf-16-leutf-16-be 作为编码的一部分。

这很清楚:

>>> 'abc'.encode('utf-16').hex()
'fffe610062006300'
>>> 'abc'.encode('utf-16-le').hex()
'610062006300'
>>> 'abc'.encode('utf-16-be').hex()
'006100620063'

这个观察

... what really surprises me that I can decode b1 and b2 and they both reconstitute to the original string:

b1.decode('utf-16') == b2.decode('utf-16')
True

建议有一个内置默认值,因为 16 位宽的 UTF-16 代码有两种可能的排列方式:Big and Little Endian.

通常,Python 会在读取时从 BOM 中推断出要使用的字节顺序 - 因此在写入时它总是加一。如果你想强制特定的字节顺序,你可以使用显式编码 utf-16-leutf-16-be:

… when such an encoding is used, the BOM will be automatically written as the first character and will be silently dropped when the file is read. There are variants of these encodings, such as ‘utf-16-le’ and ‘utf-16-be’ for little-endian and big-endian encodings, that specify one particular byte ordering and don’t skip the BOM.
(https://docs.python.org/3/howto/unicode.html#reading-and-writing-unicode-data)

但是如果您不使用特定的顺序,那么使用什么默认值?最初的 Unicode 提案 PEP 100 警告

Note: 'utf-16' should be implemented by using and requiring byte order marks (BOM) for file input/output.
(https://www.python.org/dev/peps/pep-0100/, my emph.)

但它对你有用。如果我们在 Python 源代码中查找这是如何管理的,我们会在 _codecsmodule.c:

中找到这条注释
/* This version provides access to the byteorder parameter of the
   builtin UTF-16 codecs as optional third argument. It defaults to 0
   which means: use the native byte order and prepend the data with a
   BOM mark.
*/

更深入,在 unicodeobject.c

/* Check for BOM marks (U+FEFF) in the input and adjust current
   byte order setting accordingly. In native mode, the leading BOM
   mark is skipped, in all other modes, it is copied to the output
   stream as-is (giving a ZWNBSP character). */

因此,最初,字节顺序设置为您系统的默认值,当您开始解码 UTF-16 数据并跟随一个 BOM 时,字节顺序将设置为此指定的任何值。最后一条评论中的 "native order" 指的是某个字节顺序是否已明确声明或已通过 BOM 遇到;如果两者都不成立,它将使用您系统的字节顺序。