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

时间:2011-06-16 03:51:30

标签: java windows linux character-encoding png

目前我正在尝试以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中读取文件后的示例如下:

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

答案 0 :(得分:20)

�是一个包含三个字符的序列 - 0xEF 0xBF 0xBD,是Unicode代码点0xFFFD的UTF-8表示形式。对于非法的UTF-8序列,代码点本身就是replacement character

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

然而,在Linux上,这个字节正在被UTF-8例程(或者认为将文件作为UTF-8序列处理好的库)解码。因为,它自己的0x89不是ASCII-7范围内的有效代码点(ref:the UTF-8 encoding scheme),所以它不能映射到0x00-0x7F范围内的有效UTF-8代码点。此外,它不能映射到表示为多字节UTF-8序列的有效代码点,因为所有多字节序列都以最少2位设置为1(11....)开头,因为这是文件的开头,它也不能是一个连续字节。由此产生的行为是,UTF-8解码器现在用UTF-8替换字符替换0x89 0xEF 0xBF 0xBD(考虑到文件不是很愚蠢UTF-8开头),它将ISO-8859-1显示为�

如果您需要解决此问题,则需要在Linux中确保以下内容:

  • 使用适当的文件编码(即不是UTF-8)读取PNG文件中的字节;如果您将文件作为字符序列 * 读取,这显然是必要的,如果您只是单独读取字节,则不需要。您可能正确地执行此操作,因此也值得验证后续步骤。
  • 查看文件内容时,请使用不对文件进行任何内部解码的合适编辑器/视图为UTF-8字节序列。使用合适的字体也会有所帮助,因为你可能想要防止前所未有的情况,即字形(0xFFFD它实际上是钻石字符 )无法表示,并可能导致进一步的变化(不太可能,但你永远不知道编辑/观众是如何写的。)
  • 使用合适的编码(ISO-8859-1)编写文件(如果你这样做)也是一个好主意,而不是UTF-8。如果您正在处理文件内容并将其作为字节而不是字符存储在内存中,那么将这些内容写入输出流(不涉及任何字符串或字符引用)就足够了。

* 显然,如果将字节序列转换为字符或String对象,Java Runtime将执行将字节序列解码为UTF-16代码点。

答案 1 :(得分:7)

在Java中, Stringbyte[]

  • byte[]表示原始二进制数据。
  • String表示文本,它具有关联的字符集/编码,以便能够分辨出它代表的字符。

二进制数据≠文字

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

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

\x89PNG不是文本,它只是用于识别PNG文件的“魔术”标题。 0x89甚至不是一个字符,它只是一个任意的字节值,而且显示的唯一合理的表示形式是\x890x89,...同样地,PNG实际上存在二进制数据,它也可能是0xdeadbeef并且它什么都没有改变。 PNG恰好是人类可读的这一事实只是一种便利。

你的问题来自你的协议混合文本和二进制数据这一事实,而Java(不像其他语言,如C)对二进制数据的处理方式与文本不同。

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

  • 将所有内容视为二进制数据。当您阅读整个文本行时,请使用适当的字符集/编码将其转换为String
  • InputStreamReader之上添加InputStream,在需要二进制数据时直接访问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[]

二进制数据≠文字