2012-06-20 60 views
3

在Python,例如:如果我在一个文件中放置两种编码的字符串,比如utf-8和utf-16,该怎么办?

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只) ,它可以回读确定。 所以我猜混合两种类型的编码在同一个文件是错误的,不能解码回来,即使我知道每个特定字符串的编码规则?任何建议是值得欢迎的,谢谢!

+3

不能肯定的readLine()如何应该检测行结束时,你不知道这是否是UTF -8或UTF-16。如果你能得到解决,它可能会工作。但为什么? – Thilo

+0

您的代码可用于Big Endian系统; ''\ n'.encode('utf-16-be')'==''\ x00 \ n''so.readline实际上会包含额外的字节。 –

+0

@MartijnPieters:是的,如果我将编码规则更改为'utf-16-be',它确实有效,但是能否告诉我为什么在指出big-endian BOM时工作?我试过'utf-16-le',但失败了。如果我只是使用'utf-16',这是否意味着它等于'utf-16-le'?谢谢! – nrek

回答

4

这通常是一个坏主意,但在你的情况,因为你编码换行符,以及它不工作。

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

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

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 

>>> 

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

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

+0

明白了!谢谢,我将参考python的结构部分:) – nrek

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') 
+1

不,UTF-16也会对行结尾进行编码(2个字节),因此您需要确保使用Big Endian编码,才能使用python的行结束支持。 –

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

但这不是有效的XML,对吧?解析器会发声。 – Thilo

+0

我稍微调整了它,它从来没有打算有效,只是一个例子。 –

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') 
+0

如果要写入的数据包含换行符,该怎么办? 'u'this是一个“paragrahs''的例子。 –

+0

好吧,readline()也不会工作 – AlbertFerras

相关问题