如果我将两种编码字符串(例如utf-8和utf-16)放在一个文件中怎么办?

时间:2012-06-20 07:24:17

标签: python unicode utf-8 utf-16

在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 个答案:

答案 0 :(得分:5)

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

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

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

因此,UTF-16数据流以两种顺序之一写入,首先写入Byte Order Mark或“BOM”以标记选择哪一种。

因此,您的换行符编码为'\n\x00''\x00\n',并且在读取时,空字节(\x00)是您解码的UTF-16数据的一部分,或者是UTF -8数据(忽略它)。因此,如果您将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 module来编写固定长度的数据。请注意,我也将文件写为二进制文件。

读:

>>> 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

>>>

同样,文件以二进制模式打开。

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

答案 1 :(得分: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')

答案 2 :(得分: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')

答案 3 :(得分:0)

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

<entries>
    <entry encoding="utf-16">
        <text>私</text>
        <meaning>I, myself</meaning>
    </entry>
    <entry encoding="utf-8">
        <text>あなた</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