基于本文回答
0
评论

为什么 Java字符流不能直接用来安全地读取/拷贝二进制文件(如图片、视频)?

Java 字符流(如 FileReaderFileWriter)不能用来读取或拷贝图片、视频等二进制文件的核心原因在于:字符流在读写过程中会进行“字符编码与解码(Character Encoding/Decoding)”,这个过程会导致二进制数据的不可逆损坏。

下面我们深度解析其背后的底层原因、工作原理以及会导致的具体后果。


1. 核心原因:编码/解码造成的“信息失真”

字符流的设计初衷是处理文本。它的工作流程是:

  • 读取时:将磁盘上的字节(Byte)根据某种字符集(如 UTF-8、GBK)解码成 Java 内存中的字符(Char,UTF-16 编码)。
  • 写入时:将内存中的字符根据字符集编码回对应的字节写入磁盘。

然而,二进制文件(图片、视频、PDF、MP3 等)的字节数据是任意的(范围在 0x000xFF 之间),它们并不是按照任何字符集规范组织的。当用字符流处理它们时,会发生以下致命问题:

① 无法识别的字节被替换(乱码与信息丢失)

在很多字符集(如 UTF-8)中,某些特定的字节组合是“非法”的(不符合字符编码规则)。

  • 发生什么: 当字符流遇到这些无法识别的字节时,它无法将其映射到任何字符,于是会用一个默认的占位字符来代替(通常是 Unicode 替换字符 ``,即 \uFFFD)。
  • 后果: 原本的高清图片数据被替换成了“问号”字符。当再次写入时,这些 `` 会被编码成 UTF-8 的 0xEF 0xBF 0xBD原始的二进制信息丢失,文件彻底损坏。

② 映射关系不对等(多对一或多字节合并)

  • 发生什么: 在多字节编码(如 UTF-8)中,字符流会尝试把连续的 2 个、3 个甚至 4 个字节合并解码为一个字符。
  • 后果: 原本独立的 3 个二进制字节,被强行合成为 1 个字符。写入时,由于编码器的规范,输出的字节序列可能已经和输入的完全不同。

2. 具体案例对比

假设我们有一个图片的片段,包含两个字节:[0x89, 0x50](这是 PNG 图片的标志性开头部分)。

情况 A:使用字节流(Safe)

  • 读取:直接读取 0x890x50
  • 写入:直接写入 0x890x50
  • 结果:数据 100% 一致,图片正常打开。

情况 B:使用字符流(UTF-8 编码,Unsafe)

  • 读取:字符流读取 0x89。在 UTF-8 中,0x89 是一个非法的首字节(UTF-8 多字节字符的首字节不能是 10xxxxxx 格式)。
  • 解码失败:JVM 将其识别为无效字符,替换为 `` (\uFFFD)。
  • 写入:字符流将 编码写回文件。在 UTF-8 中, 被编码为三个字节:0xEF, 0xBF, 0xBD
  • 结果:原本的 0x89 变成了 0xEF, 0xBF, 0xBD。图片文件头被破坏,图片报错损坏,无法打开

3. 其他潜在问题:平台换行符转换

有些字符流(或高级包装流,如 PrintWriterBufferedReader.readLine())会针对不同的操作系统自动处理换行符(如将 Windows 的 \r\n 转换为 Unix 的 \n,或者反过来)。

在二进制文件中,0x0D(CR)和 0x0A(LF)可能仅仅代表图片中的两个像素点颜色,一旦被当作换行符进行了转换,文件结构立刻错位,彻底报废。


Summary:总结与正确做法

特性 字节流 (Byte Stream) 字符流 (Character Stream)
代表类 InputStream, OutputStream Reader, Writer
操作单位 字节 (byte / 8-bit) 字符 (char / 16-bit)
编码转换 (原样搬运,最安全) (按字符集解码/编码)
适用场景 视频、图片、音频、压缩包、任何二进制文件 纯文本文件(TXT, HTML, CSV)

黄金法则:

  • 只要文件能用“记事本”打开且内容看得懂的(文本),用字符流
  • 只要是记事本打开是乱码的(图片、视频、可执行文件等),必须使用字节流

拷贝二进制文件的正确代码示例(Java 7+):

java
// 使用 Java NIO,高效且安全
Path source = Paths.get("input.jpg");
Path target = Paths.get("output.jpg");
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
右滑查看面试常问