读取UTF-16文件的Python 3.x似乎按字节顺序反转

时间:2018-07-19 16:56:26

标签: python python-3.x utf-16

我正在尝试使用Python读取Windows生成的UTF-16文件。据我了解,BOM是FEFF。这就是该文件的开头。但是,当我将文件读入Python时,字节似乎被交换了。

(venv) [user]:~/consolidate$ head -c 16 temp.txt | od -x
0000000 feff 0022 0076 0065 0072 0073 0069 006f
0000020
(venv) [user]:~/consolidate$ python
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> with open('temp.txt', 'rb') as f:
...     str = f.readline()
...     print(str)
...
b'\xff\xfe"\x00v\x00e\x00r\x00s\x00i\x00o\x00n...

使用head,第一个字符为feff 0022。使用Python,它似乎是fffe2200。这是怎么回事?

编辑:我的问题专门关于字节顺序。几点:

  • 我不想解码文件。这是一个10GB的文件,需要按一定顺序进行拆分。
  • 它似乎只在第一行。
  • 写回文件将保留原始顺序。

第二行示例:

>>> with open('temp.txt', 'rb') as f:
...     str1 = f.readline()
...     str2 = f.readline()
...
>>> str2
b'\x00"\x00"\x00`\x00"\x00P\x

3 个答案:

答案 0 :(得分:1)

这里发生了三个独立的类似事情。该文件是一个 bytes 序列,并且Python字节字符串b'\xff\xfe"\x00v\x00e\x00...'以字节在文件中的顺序显示内容:

FF FE 22 00 76 00 65 00

运行od -x时,它将成对的字节分为 16位数字。在x86系统上,对于2字节16位数字,标准字节顺序是最低有效字节(“一个字节”)在第一位,最高有效字节(“ 256s字节”)在第二位(在Python中, n=b[0]+256*b[1])。这样就得到了 little-endian 解码:

FEFF  0022  0076  0065

同时,您希望将其解码为Unicode 字符。只要U + FFFF上方没有字符, UTF-16小尾数(UTF-16LE)编码会将相同的解码转换为Unicode字符:

U+FEFF U+0022 U+0076 U+0065
<BOM>     "      v      e

行尾会发生什么?让我们考虑字符串u'...",\n ...',并以相反的顺序进行此练习。

   "      ,     \n   <SPC>
U+0022 U+002C U+000A U+0020
22 00  2C 00  0A 00  20 00
b'"\x00,\x00\n\x00 \x00'

同时:如果您实际上不考虑字符编码并“将其拆分为换行符”会发生什么?您会看到[b'"\x00,\x00"', b'\n', b'\x00 \x00']。看起来第一部分是little-endian字节顺序(quote null,逗号null),但是最后一部分是big-endian(null空间)。但是后半部分实际上不是有效的UTF-16字符串:它包含奇数个字节,因为前一个字节实际上是换行符的后半部分。呼叫readline时就是这种情况。

您可以选择几种方法来解决此问题。在另一个答案中提到的一个是open(filename, 'r', encoding='utf-16')(在文件模式下不带“ b”)。然后,Python将进行正确的UTF-16解码(考虑字节顺序标记),您将获得一个字符串。像str.readline这样的电话也可以满足您的期望。

您还说过,您的目标只是拆分文件。如果您绝对确定文件是UTF-16LE编码的(前两个字节肯定是FF FE),则可以将其作为字节字符串处理(如问题代码中的模式'rb'),然后将其拆分为所需的UTF-16编码字节序列

everything = f.read()
lines = everything.split(b'\x0A\x00')
for line in lines:
  parts = line.split(b'\x3A\x26')

如果您可以一整块地读取整个文件,则这样做更容易; 10 GB时,在Python中可能会有些棘手。

答案 1 :(得分:0)

添加encoding ='utf-16'参数以使用
open('temp.txt', 'r', encoding='utf-16')

打开

答案 2 :(得分:0)

您可以使用utf-16-le显式解码为低端,并按预期方式收到BOM:

>>> b'\xff\xfe"\x00v\x00e\x00r\x00s\x00i\x00o\x00n\x00'.decode('utf-16-le')
'\ufeff"version'

如果您使用utf-16进行解码,则它已经删除了BOM表:

>>> b'\xff\xfe"\x00v\x00e\x00r\x00s\x00i\x00o\x00n\x00'.decode('utf-16')
'"version'