目前我正在尝试读取一个MIME格式的文件,其中包含一些PNG的二进制string数据。
在Windows中,读取文件给了我适当的二进制string,这意味着我只是复制string并将扩展名更改为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”符号。
任何人都可以启发我正在发生的事情,也许提供一个解决scheme? 现在玩了一个多星期的代码。
是三个字符的序列 – 0xEF
0xFFFD
,并且是Unicode代码点0xFFFD
UTF-8表示。 代码本身就是非法UTF-8序列的替代字符 。
显然,出于某种原因,你的源代码(在Linux上)所涉及的一系列例程不正确地处理PNG头文件。 PNG头文件以字节0x89
开头(后面跟着0x4E
, 0x47
),在Windows中正确处理(可能将文件视为CP1252字节序列)。 在CP1252中 , 0x89
字符显示为‰
。
然而,在Linux上,这个字节被一个UTF-8例程(或者一个认为把文件作为一个UTF-8序列处理的好的库)解码。 由于它自己的0x89不是ASCII-7范围内的有效代码点(ref: UTF-8编码方案 ),因此无法将其映射到0x00-0x7F范围内的有效UTF-8代码点。 此外,它不能映射到一个有效的代码点,表示为一个多字节的UTF-8序列,对于所有的多字节序列,最小2位设置为1( 11....
),因为这是文件的开始,它也不能作为连续字节。 由此产生的行为是,UTF-8解码器现在用UTF-8替换字符0xEF
0xBF
0xBD
(考虑到该文件不是以UTF-8开头)替换0x89
,这将在ISO-8859中显示-1如同。
如果您需要解决此问题,则需要在Linux中确保以下内容:
0xFFFD
实际上是钻石字符 )无法表示,并且可能会导致进一步的变化(不太可能,但您永远不知道编辑器/查看器已被写入)。 *显然,如果将字节序列转换为字符或字符串对象,则Java运行时将执行将字节序列解码为UTF-16码点。
在Java中, String
≠ byte[]
。
byte[]
表示原始二进制数据。 String
表示文本,它具有关联的字符集/编码,以便能够分辨它所表示的字符。 二进制数据≠文本 。
String
文本数据具有Unicode / UTF-16作为字符集/编码(或序列化时为Unicode / mUTF-8)。 每当你从一个不是String
转换成String
或反之亦然时,你需要为非String
文本数据指定一个字符集/编码(即使你使用平台的默认字符集隐式地进行编码)。
PNG文件包含表示图像(和相关元数据)的原始二进制数据,而不是文本。 因此,你不应该把它当作文本。
\x89PNG
不是文字,只是识别PNG文件的“魔术”标题。 0x89
甚至不是一个字符,它只是一个任意的字节值,并且它唯一的显示表达式是\x89
,…类似的, PNG
实际上存在二进制数据,也可能是0xdeadbeef
而且它什么都不会改变。 PNG
碰巧是人类可读的事实只是一个方便。
你的问题来自于你的协议混合了文本和二进制数据,而Java(不同于其他一些语言,比如C)不同于文本处理二进制数据。
Java提供*InputStream
读取二进制数据, *Reader
读取文本。 我看到两种处理输入的方法:
String
。 InputStreamReader
之上InputStream
,在需要二进制数据时直接访问InputStreamReader
当需要文本时访问InputStreamReader
。 你可能想缓冲,把它放在第二种情况下的正确位置是*Reader
下面。 如果你使用了BufferedReader
, BufferedReader
可能会比InputStream
消耗更多的输入。 所以,你会有这样的事情:
┌───────────────────┐ │ InputStreamReader │ └───────────────────┘ ↓ ┌─────────────────────┐ │ BufferedInputStream │ └─────────────────────┘ ↓ ┌─────────────┐ │ InputStream │ └─────────────┘
您将使用InputStreamReader
读取文本,然后使用BufferedInputStream
从同一个流中读取适当数量的二进制数据。
一个有问题的情况是将"\r"
(旧的MacOS)和"\r\n"
(DOS / Windows)识别为行结束符。 在这种情况下,你最终可能会读一个字符太多。 您可以采取不赞成的DataInputStream.readline()
方法所采用的方法:将内部InputStream
透明地包装到PushbackInputStream
并且不读取该字符。
但是,由于您看起来没有内容长度 ,因此我会推荐第一种方法,将所有内容视为二进制文件,并在读取完整行后才转换为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[]
。
二进制数据≠文本 。