Python 3.x 读取 UTF-16 文件似乎颠倒了字节顺序
Python 3.x reading UTF-16 files seems to reverse byte order
我正在尝试使用 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
添加编码='utf-16'参数以
打开open('temp.txt', 'r', encoding='utf-16')
您可以使用 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'
这里发生了三件独立的类似事情。该文件是 字节 的序列,Python 字节字符串 b'\xff\xfe"\x00v\x00e\x00...'
显示的内容与字节在文件中的顺序相同:
FF FE 22 00 76 00 65 00
当您 运行 od -x
时,它会将字节对分组为 16 位数字。在 x86 系统上,2 字节 16 位数字的标准字节顺序是最低有效字节 ("ones byte") 排在第一位,最高有效字节(“256s 字节”)排在第二位(在 Python, n=b[0]+256*b[1]
).所以你得到这个 little-endian 解码:
FEFF 0022 0076 0065
同时,您想将其解码为 Unicode 个字符 。只要没有字符在 U+FFFF 以上,UTF-16 little-endian (UTF-16LE) 编码 t运行 将相同的解码为 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'
同时:如果您实际上不考虑字符编码会发生什么,"split this on newlines"?你会看到 [b'"\x00,\x00"', b'\n', b'\x00 \x00']
。看起来第一部分是小端字节序(引用空逗号 null)但最后一部分是大端字节序(空 space)。但后半部分实际上不是有效的 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.
中可能会很棘手
我正在尝试使用 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
添加编码='utf-16'参数以
打开open('temp.txt', 'r', encoding='utf-16')
您可以使用 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'
这里发生了三件独立的类似事情。该文件是 字节 的序列,Python 字节字符串 b'\xff\xfe"\x00v\x00e\x00...'
显示的内容与字节在文件中的顺序相同:
FF FE 22 00 76 00 65 00
当您 运行 od -x
时,它会将字节对分组为 16 位数字。在 x86 系统上,2 字节 16 位数字的标准字节顺序是最低有效字节 ("ones byte") 排在第一位,最高有效字节(“256s 字节”)排在第二位(在 Python, n=b[0]+256*b[1]
).所以你得到这个 little-endian 解码:
FEFF 0022 0076 0065
同时,您想将其解码为 Unicode 个字符 。只要没有字符在 U+FFFF 以上,UTF-16 little-endian (UTF-16LE) 编码 t运行 将相同的解码为 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'
同时:如果您实际上不考虑字符编码会发生什么,"split this on newlines"?你会看到 [b'"\x00,\x00"', b'\n', b'\x00 \x00']
。看起来第一部分是小端字节序(引用空逗号 null)但最后一部分是大端字节序(空 space)。但后半部分实际上不是有效的 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.
中可能会很棘手