【问题标题】:Reading File from Windows and Linux yields different results (character encoding?)从 Windows 和 Linux 读取文件会产生不同的结果(字符编码?)
【发布时间】:2011-06-16 03:51:30
【问题描述】:

目前我正在尝试读取具有一些 png 二进制字符串数据的 mime 格式的文件。

在 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”符号。

谁能告诉我正在发生的事情并可能提供解决方案?一个星期以来一直在玩代码。

【问题讨论】:

  • 请向我们展示重现问题的代码。
  • 原来这个问题本身与 PNG 文件无关,它是 Unicode 字符的一个更广泛的问题。此外,这里的答案与语言无关:Java、Python 等。

标签: java unicode character-encoding png


【解决方案1】:

� 是三个字符的序列 - 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 范围内的有效代码点(参考:the UTF-8 encoding scheme),因此它不能映射到 0x00-0x7F 范围内的有效 UTF-8 代码点。此外,它不能映射到表示为多字节 UTF-8 序列的有效代码点,因为所有多字节序列都以至少 2 位设置为 1 (11....) 开始,因为这是开始对于文件,它也不能是连续字节。产生的行为是 UTF-8 解码器现在用 UTF-8 替换字符 0xEF 0xBF 0xBD 替换 0x89 (考虑到文件不是以 UTF-8 开头的,这太愚蠢了),这将在ISO-8859-1 中显示为�

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

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

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

【讨论】:

  • 嗨,Vineet,写得很棒!我想做的事情是将这些部分拆分为 String[] 来操作数据,因为我需要将 png 二进制文件编码为 base64。要测试这个。谢谢!
  • 我认为您不需要拆分文件。您可以将字节流输入到像 Apache Commons Codec 这样的 Base64 编码器,它会为您完成这项工作。需要以适当的编码读取文件。
  • 有更合适的编码。该文件应作为字节序列读取。
  • @ninjalj,你是对的。我误解了。我指的是 FileOutputStream 而不是 FileInputStream。编辑:又错了。我的意思是 InputStreamReader 和 OutputStreamWriter。如果从具有不同编码的流​​中读取,还有一个 String 对象可以是不同的字符集。
  • 大家好,请不要将评论系统用作聊天室。这是为了给问题或答案留下一些信息,而不是为了长时间的辩论。这背后的原因是大多数时候(这就是其中之一),如果不是所有的 cmets 都属于对问题/答案的编辑,以使其更完整。如果非要读半页答案+3页cmets的话,cmets的关注点太大了。请改为将相关详细信息编辑到答案中。如果你真的需要聊天,在聊天网站上找到/创建一个聊天室,链接在页面顶部
【解决方案2】:

在 Java 中,Stringbyte[]

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

二进制数据≠文本

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

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

\x89PNG 不是文本,它只是一个用于识别 PNG 文件的“神奇”标题。 0x89 甚至不是一个字符,它只是一个任意字节值,它对 display 的唯一合理表示是 \x89, 0x89, ... 同样,PNG实际上存在二进制数据,它也可能是0xdeadbeef,它不会改变任何东西。 PNG 恰好是人类可读的这一事实只是为了方便。

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

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

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

您可能需要缓冲,在第二种情况下放置它的正确位置是在*Reader 下方。如果您使用了BufferedReaderBufferedReader 可能会消耗更多来自InputStream 的输入。所以,你会有类似的东西:

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

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

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

但是,由于您似乎没有 Content-Length,我会推荐第一种方法,将所有内容都视为二进制,并仅在阅读整行后转换为 String。在这种情况下,我会将 MIME 分隔符视为二进制数据。

输出:

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

或者您的数据可能必须在将其视为文本的通道上传输。 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[]

二进制数据≠文本

【讨论】:

  • 这应该是公认的答案。 “PNG 文件的合适编码”与“TXT 文件的调色板”一样荒谬。
猜你喜欢
  • 2021-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-27
  • 1970-01-01
  • 2018-09-13
相关资源
最近更新 更多