【问题标题】:Python 3.x reading UTF-16 files seems to reverse byte orderPython 3.x 读取 UTF-16 文件似乎颠倒了字节顺序
【发布时间】:2018-07-19 16:56:26
【问题描述】:

我正在尝试使用 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

【问题讨论】:

  • b’...’ 是一个字节流,您看到的是文件中字节的顺序,而不是解码后的字符。
  • 看起来像字节序问题。根据@Mr.的回答。下面的J,如果指定编码,Python应该会自动识别是UTF-16LE还是UTF16-BE
  • @DavidMaze 我的问题是关于字节顺序的。为什么看起来好像变了?
  • 因为 od -x 似乎正在以 little-endian 字节交换顺序解码字节对。 od -t x1 应该以与问题中相同的顺序转储字节。

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


【解决方案1】:

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

FF FE 22 00 76 00 65 00

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

FEFF  0022  0076  0065

同时,您希望将其解码为 Unicode 字符。只要没有字符高于 U+FFFF,UTF-16 little-endian (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 字节顺序(引用 null 逗号 null),但最后一部分是 big-endian(空空间)。但后半部分实际上不是有效的 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 中可能会很棘手。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      您可以使用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'
      

      【讨论】:

        猜你喜欢
        • 2019-01-12
        • 2012-05-01
        • 2021-02-13
        • 2012-04-15
        • 1970-01-01
        • 1970-01-01
        • 2021-06-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多