目前我正在尝试以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”符号。
任何人都可以告诉我发生了什么,也许可以提供解决方案吗?已经玩了一周以上的代码了。
答案 0 :(得分:20)
�
是一个包含三个字符的序列 - 0xEF
0xBF
0xBD
,是Unicode代码点0xFFFD
的UTF-8表示形式。对于非法的UTF-8序列,代码点本身就是replacement character。
显然,出于某种原因,源代码(在Linux上)中涉及的一组例程正在不准确地处理PNG标头。 PNG header以字节0x89
开头(后跟0x50
,0x4E
,0x47
),这在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中确保以下内容:
0xFFFD
它实际上是钻石字符 )无法表示,并可能导致进一步的变化(不太可能,但你永远不知道编辑/观众是如何写的。)* 显然,如果将字节序列转换为字符或String对象,Java Runtime将执行将字节序列解码为UTF-16代码点。
答案 1 :(得分:7)
在Java中, String
≠byte[]
。
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
。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中, String
≠byte[]
。
二进制数据≠文字。