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

Mau*_*ice 6 java linux windows png character-encoding

目前我正在尝试以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
Run Code Online (Sandbox Code Playgroud)

等...等...

在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
Run Code Online (Sandbox Code Playgroud)

等...等...


我无法将Linux版本转换成图片,因为它变成了一些时髦的符号,并且有很多颠倒的"?" 和"1/2"符号.

任何人都可以告诉我发生了什么,也许可以提供解决方案?已经玩了一周以上的代码了.

Vin*_*lds 20

�是一个由三个字符组成的序列 - 0xEF 0xBF 0xBD并且是Unicode代码点的UTF-8表示0xFFFD.代码点本身就是非法UTF-8序列的替换字符.

显然,出于某种原因,源代码中涉及的一组例程(在Linux上)正在不准确地处理PNG头.该PNG头与所述字节开始0x89(和后跟0x50,0x4E,0x47),这是在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解码器现在替换0x89为UTF-8替换字符0xEF 0xBF 0xBD(考虑到文件不是UTF-8开始时有多愚蠢),这将在ISO-8859-1中显示为�.

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

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

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

  • 嗨伙计们,请不要将评论系统用作聊天室.这是留下一些评论和刺激,以获得更多信息的问题或答案,而不是长时间的辩论.这背后的原因是大部分时间(这是其中之一),很多(如果不是所有的)评论都属于问题/答案的编辑,以使其更完整.如果我必须阅读半页答案+ 3页评论,那么对评论的关注就太大了.请将相关详细信息编辑到答案中.如果您确实需要聊天,请在聊天网站上查找/创建聊天室,链接在页面顶部 (2认同)

nin*_*alj 7

在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,使用适当的字符集/编码将其转换为a .
  • a层在a InputStreamReader之上,当你想要二进制数据时直接InputStream访问InputStream,访问InputStreamReader你想要的文本.

你可能想要缓冲,把它放在第二种情况下的正确位置是在*Reader.如果你使用了a BufferedReader,BufferedReader那么它可能会消耗更多的输入InputStream.所以,你会有类似的东西:

 ?????????????????????
 ? InputStreamReader ?
 ?????????????????????
          ?
???????????????????????
? BufferedInputStream ?
???????????????????????
          ?
   ???????????????
   ? InputStream ?
   ???????????????
Run Code Online (Sandbox Code Playgroud)

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

一个有问题的案例是将"\r"(旧的MacOS)和"\r\n"(DOS/Windows)都识别为行终止符.在这种情况下,你最终可能会过多地阅读一个角色.您可以采用弃用DataInputStream.readline()方法所采用的方法:透明地将内部包装InputStreamPushbackInputStream未读取的字符中.

但是,由于您似乎没有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);
Run Code Online (Sandbox Code Playgroud)

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

结论:

在Java中,Stringbyte[].

二进制数据≠文本.

  • 这应该是公认的答案。“适合 PNG 文件的编码”与“TXT 文件的调色板”一样荒谬。 (2认同)