3

在 Python 中,例如:

f = open('test','w')
f.write('this is a test\n'.encode('utf-16'))
f.write('another test\n'.encode('utf-8'))
f.close()

当我重新打开该文件时,它会变得混乱:

f = open("test")
print f.readline().decode('utf-16')  # it leads to UnicodeDecodeError
print f.readline().decode('utf-8')   # it works fine

但是,如果我将文本编码为一种样式(仅说 utf-16),则可以正常读取。所以我猜在同一个文件中混合两种类型的编码是错误的并且不能被解码回来,即使我知道每个特定字符串的编码规则?欢迎任何建议,谢谢!

4

4 回答 4

5

这通常是一个坏主意,但在您的情况下它不起作用,因为您也编码换行符。

在 UTF-16 中,每个字符都被编码为两个字节,包括您编写的换行符。因为您逐行读取文件,python 将为您提供文件中的所有数据,直到下一个换行字节,但在 UTF-16 中,这可能意味着两个字节之一仍包含在返回的数据中,导致不完整UTF-16 字节流。

要理解这一点,您需要更详细地了解 UTF-16 编码。当将 16 位数据写为 8 位的 2 个字节时,计算机需要决定先将哪个字节写入文件。这个决定可以有两种方式,称为endianess;像 Gulliver 的 Lilliputs 一样,计算机系统更喜欢 Big endian 或 Little endian 排序。

因此,UTF-16 数据流以两种顺序之一写入,并且首先写入字节顺序标记或“BOM”以标记选择了哪个。

因此,您的换行符要么被编码为'\n\x00'or '\x00\n',并且在读取该空字节 ( \x00) 时要么是您解码的 UTF-16 数据的一部分,要么是 UTF-8 数据(它被忽略的地方)。因此,如果您将 UTF-16 编码为大端序,则一切正常(但您有一个杂散的空字节),但如果您将 UTF-16 编码为小端序,事情就会中断。

基本上,编码数据应该被严格地视为二进制数据,并且您应该使用不同的方法来描绘不同的编码文本片段,或者您应该只使用将换行符严格编码为换行符的编码。

我会使用长度前缀,先读取它,然后从文件中读取每个编码数据的字节数。

>>> import struct
>>> f = open('test', 'wb')
>>> entry1 = 'this is a test\n'.encode('utf-16')
>>> struct.pack('!h', len(entry1)))
>>> f.write(entry1)
>>> entry2 = 'another test\n'.encode('utf-8')
>>> f.write(struct.pack('!h', len(entry2)))
>>> f.write(entry2)
>>> f.close()

我已经使用该struct模块来编写定长长度的数据。请注意,我也将文件编写为二进制文件。

阅读:

>>> f = open('test', 'rb')
>>> fieldsize = struct.calcsize('!h')
>>> length = struct.unpack('!h', f.read(fieldsize))[0]
>>> print f.read(length).decode('utf-16')
this is a test

>>> length = struct.unpack('!h', f.read(fieldsize))[0]
>>> print f.read(length).decode('utf-8')
another test

>>>

该文件再次以二进制模式打开。

在现实生活中的应用程序中,您可能还必须包含每个条目的编码信息。

于 2012-06-20T08:48:58.403 回答
1

您的代码的工作版本。基本上不编码换行符,并在调用 readline() 方法时删除它们:

f = open('test','w')
f.write('this is a test'.encode('utf-16'))
f.write("\n")
f.write('another test'.encode('utf-8'))
f.write("\n")
f.close()

f = open("test")
print f.readline().strip("\n").decode('utf-16')
print f.readline().strip("\n").decode('utf-8')
于 2012-06-20T09:29:09.940 回答
0

你不能在行首使用一些标记吗?

>>> f = open('test','w')
f.write('16 - this is a test\n'.encode('utf-16'))
f.write('8 - another test\n'.encode('utf-8'))
f.close()
>>> f = open('test')
>>> for line in f:
    if line.startswith('8 - '):
        print line.replace('8 - ', '').decode('utf-8')
    elif line.startswith('16'):
        print line.replace('16 - ', '').decode('utf-16')
于 2012-06-20T07:34:19.030 回答
0

人们普遍认为,在同一个文件中使用两种不同的编码是一个坏主意。我认为它可能有用的唯一一次是如果您有一个结构化文件(例如 XML.JSON 等),其中元素可以指定编码。

<entries>
    <entry encoding="utf-16">
        <text>私&lt;/text>
        <meaning>I, myself</meaning>
    </entry>
    <entry encoding="utf-8">
        <text>あなた&lt;/text>
        <meaning>you, yourself</meaning>
    </entry>
</entries>

伪代码:

for entry in entries:
    text += entry.text.decode(entry.encoding)

此外,您的示例失败了,因为您的系统是 Little Endian 并且 readline 在 utf-16 字符中间断行。这导致第一行缺少最后一个 \x00 并且最后一行被 utf-16 行中的 \x00 前置。这是快速修复:

f = open("test")
print (f.readline()+'\x00').decode('utf-16')  # it leads to UnicodeDecodeError
print f.readline()[1:].decode('utf-8')   # it works fine
于 2012-06-20T07:35:53.553 回答