从Windows和Linux读取文件会产生不同的结果(字符编码?)

7

目前我正在尝试读取一个MIME格式的文件,其中包含一些png的二进制字符串数据。

在Windows中,读取该文件会给出正确的二进制字符串,这意味着我只需复制该字符串并将扩展名更改为png即可看到图片。


在Windows中读取文件后的示例如下:

    --fh-mms-multipart-next-part-1308191573195-0-53229
     Content-Type: image/png;name=app_icon.png
     Content-ID: "<app_icon>"
     content-location: app_icon.png

    ‰PNG

在Linux中读取文件后的示例如下:

etc...etc...

    --fh-mms-multipart-next-part-1308191573195-0-53229
     Content-Type: image/png;name=app_icon.png
     Content-ID: "<app_icon>"
     content-location: app_icon.png

     �PNG

我无法将Linux版本转换为图像,因为它变成了一些奇怪的符号,有很多倒置的“?”和“1/2”符号。

有人能给我解释一下发生了什么事情,也许提供一个解决方案吗?我已经花了一周甚至更长时间来尝试修改代码了。


2
请展示能够重现问题的代码。 - Buhb
事实证明,这个问题并不是关于PNG文件本身的,而是与Unicode字符有关的更广泛问题。此外,这里的答案相当于语言无关的:Java,Python等都可以使用。 - smci
2个回答

22

�是由三个字符组成的序列- 0xEF 0xBF 0xBD,是Unicode代码点0xFFFD的UTF-8表示形式。该码点本身是非法UTF-8序列的替换字符

显然,由于某种原因,涉及您源代码的一组例程(在Linux上)不准确地处理PNG头。 PNG头以字节0x89开头(后面跟着0x500x4E0x47),在Windows中正确处理(可能将文件视为CP1252字节序列)。在CP1252中,0x89字符显示为

在Linux上,这个字节被UTF-8例程(或者一个认为将文件处理成UTF-8序列是有好处的库)解码。由于0x89本身不是ASCII-7范围内的有效代码点(参见UTF-8编码方案),因此它不能映射到0x00-0x7F范围内的有效UTF-8代码点。同时,它也不能映射到表示为多字节UTF-8序列的有效代码点,因为所有的多字节序列都以至少2位设置为1的方式开始(11....),而且由于这是文件的开头,它也不能是续行字节。结果表现为UTF-8解码器用UTF-8替换字符0xEF0xBF0xBD(考虑到文件一开始就不是UTF-8,这太傻了)来替换0x89,在ISO-8859-1中会显示为�
如果您需要解决这个问题,在Linux中需要确保以下内容:
  • 使用适当的编码(不是UTF-8)读取PNG文件中的字节;如果您将文件视为字符序列,则显然需要这样做*,如果您仅读取字节,则无需这样做。您可能已经正确执行了此步骤,因此值得验证后续步骤。
  • 在查看文件内容时,请使用适当的编辑器/查看器,不要将文件内部解码为UTF-8字节序列。使用适当的字体也有助于防止出现无法表示图形的前所未有的情况(对于0xFFFD,它实际上是钻石字符�),并可能导致进一步的更改(不太可能,但您永远不知道编辑器/查看器是如何编写的)。
  • 如果您正在将文件写出(如果确实需要),则最好使用适当的编码(例如ISO-8859-1)而不是UTF-8。如果您将文件内容处理并存储为字节而不是字符,则将其写入输出流(不涉及任何String或字符引用)即可。

* 显然,如果您将字节序列转换为字符或String对象,则Java运行时将对其进行UTF-16代码点解码。


嗨Vineet, 写得很好!我想做的事情是将部分拆分为String[]以操纵数据,因为我需要将png二进制编码为base64。我要测试一下。 谢谢! - Maurice
我认为您不需要拆分文件。您可以将字节流提供给像Apache Commons Codec这样的Base64编码器,它会为您完成工作。必要的是以适当的编码读取文件。 - Vineet Reynolds
1
没有适当的编码方式。该文件应该被读取为一系列字节。 - ninjalj
可能确定您是否正确进行Base64编码(在正确的编码方式下)的一种可能方法是,将其解码回二进制文件,并将内容与PNG文件的原始二进制数据进行匹配。 - Vineet Reynolds
2
大家好,请不要把评论系统当成聊天室。它是用来留下一些评论和提示,以获取更多关于问题或答案的信息,而不是用来进行长时间的辩论。这样做的原因是大部分时间(包括现在),如果不是所有的评论都应该作为编辑添加到问题/答案中,以使其更完整。如果我必须阅读半页的答案+3页的评论,那么评论的重点就太大了。请将相关细节编辑到答案中。如果你真的需要聊天,请在聊天网站上找到/创建一个聊天室,链接在页面顶部。 - Lasse V. Karlsen
显示剩余29条评论

8
在Java中,Stringbyte[]
  • byte [] 表示原始二进制数据。
  • String表示文本,具有关联的字符集/编码,以便能够告诉它表示哪些字符。

二进制数据≠文本

String内的文本数据具有Unicode / UTF-16作为字符集/编码(或在序列化时为Unicode / mUTF-8)。每当您从不是String的东西转换为String或反之亦然时,都需要为非String文本数据指定一个字符集/编码(即使您使用隐式地执行此操作 使用平台的默认字符集)。

PNG文件包含表示图像(和相关元数据)的原始二进制数据,而不是文本。因此,您不应将其视为文本。

\ x89PNG 不是文本,只是用于标识PNG文件的“魔术”标题。 0x89 甚至不是字符,只是任意字节值,其唯一的理智表示形式显示是类似于 \ x89 0x89 ,...同样,其中的PNG在现实中是二进制数据,它也可能是0xdeadbeef,它不会改变任何东西。 PNG之所以可以由人类阅读,只是一种方便。

您的问题源于您的协议混合了文本和二进制数据,而Java(与一些其他语言(如C)不同)将二进制数据与文本区分对待。

Java为读取二进制数据提供* InputStream ,并为读取文本提供* Reader 。我看到处理输入的两种方法:

  • 将所有内容视为二进制数据。当您读取整个文本行时,使用适当的字符集/编码将其转换为String
  • InputStream 上叠加InputStreamReader ,直接访问InputStream 以获取二进制数据,在需要文本时访问InputStreamReader

如果您需要缓冲,第二种情况下正确的放置位置是在*Reader下面。如果您使用了BufferedReader,则BufferedReader可能会从InputStream中消耗更多的输入,因此您将会得到以下代码:

 ┌───────────────────┐
 │ InputStreamReader │
 └───────────────────┘
          ↓
┌─────────────────────┐
│ BufferedInputStream │
└─────────────────────┘
          ↓
   ┌─────────────┐
   │ InputStream │
   └─────────────┘

您需要使用InputStreamReader读取文本,然后使用BufferedInputStream从同一流中读取适当数量的二进制数据。

一个问题是识别"\r"(旧版MacOS)和"\r\n"(DOS / Windows)作为行终止符。在这种情况下,您可能会多读一个字符。您可以采取已弃用的DataInputStream.readline()方法所采用的方法:将内部InputStream透明地包装成PushbackInputStream并取消读取该字符。

但是,由于您似乎没有Content-Length,因此我建议采用第一种方式,将所有内容视为二进制,并在读取整行后仅转换为String。在这种情况下,我将MIME分隔符视为二进制数据。

输出:

由于您正在处理二进制数据,因此不能只使用println()PrintStream具有write()方法,可以处理二进制数据(例如:用于输出到二进制文件)。

或者,您的数据必须在将其视为文本的通道上传输。 Base64专为这种情况而设计(将二进制数据作为ASCII文本传输)。 Base64编码形式仅使用US_ASCII字符,因此您应该能够在任何是US_ASCII的超集(ISO-8859- *,UTF-8,CP-1252等)的字符集/编码中使用它。由于您正在将二进制数据转换为/从文本,因此Base64的唯一合理API应该是:

String Base64Encode(byte[] data);
byte[] Base64Decode(String encodedData);

这基本上就是内部java.util.prefs.Base64使用的内容。

结论:

在Java中,Stringbyte[]

二进制数据 ≠ 文本


2
这应该是被接受的答案。对于PNG文件来说,“合适的编码”就像“TXT文件的调色板”一样毫无意义。 - dan04

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接