CodeGym /课程 /JAVA 25 SELF /二进制文件的读写:InputStream、OutputStream

二进制文件的读写:InputStream、OutputStream

JAVA 25 SELF
第 36 级, 课程 2
可用

1. 引言

我们先从最简单的说起:二进制文件与文本文件有什么不同?文本文件是可以用普通的记事本打开并看到字母、数字、空格和其他符号的文件。例如,my_notes.txt 或 poem.txt。

二进制文件是包含任意字节而非文本的文件。它可能是图片(.jpg、.png)、音乐(.mp3)、压缩包(.zip)、可执行文件(.exe)、视频(.mp4)、数据库文件等。如果你在记事本中打开这类文件,会看到类似 ÿØÿà 的东西,或一整屏莫名其妙的符号。这很正常!计算机“理解”的只有字节——对它来说,不论文本、图片还是视频,都只是字节序列。在文本文件中,这些字节可以被解释为字符;而在二进制文件中,它们是给程序处理的“原始”数据,不是给人阅读的。

用于处理二进制文件的基本类

在 Java 中,处理二进制文件使用字节流:

  • InputStream — 读取字节的基类。
  • OutputStream — 写入字节的基类。

用于文件的具体实现有:

  • FileInputStream — 从文件读取字节。
  • FileOutputStream — 将字节写入文件。

如果你听说过 FileReader 和 FileWriter,要知道:它们处理的是字符,只适用于文本。对于二进制文件,请只使用 InputStream/OutputStream 及其子类。

2. 读取二进制文件

逐字节读取

最简单的方法是逐字节读取。它直观,但非常慢。

try (FileInputStream in = new FileInputStream("image.jpg")) {
    int b;
    while ((b = in.read()) != -1) {
        // b 是 0 到 255 的数值(字节),-1 表示文件结束
        // 可以处理该字节,例如统计所有字节的总和
    }
}

方法 read() 将下一个字节以 int 返回(从 0 到 255),当读到文件末尾时返回 -1。通常只有在需要做非常特定的事情(例如分析文件结构)时才逐字节读取。

分块读取(带缓冲)

逐字节读取就像每次只去商店买一个苹果。更高效的做法是一次拿一整袋!在 Java 中,可以使用 read(byte[] buffer) 方法,它会用文件中的字节填充数组。

try (FileInputStream in = new FileInputStream("image.jpg")) {
    byte[] buffer = new byte[4096]; // 4 KB 的缓冲区
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        // buffer 中包含从文件读取的 bytesRead 个字节
        // 可以处理这些字节,例如把它们保存到其他地方
    }
}

方法 read(buffer) 会返回实际读取了多少字节(可能小于缓冲区大小,特别是在最后一次读取时)。这种方式要快得多,因为与磁盘的交互更少。

示例:复制文件

我们来写一个简单的程序,把任意二进制文件(例如图片)从一个位置复制到另一个位置。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BinaryCopyExample {
    public static void main(String[] args) {
        String source = "cat.jpg";
        String dest = "cat_copy.jpg";

        try (FileInputStream in = new FileInputStream(source);
             FileOutputStream out = new FileOutputStream(dest)) {

            byte[] buffer = new byte[8192]; // 对于大多数场景,8 KB 是较优的大小
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            System.out.println("复制完成!");
        } catch (IOException e) {
            System.out.println("复制时出错:" + e.getMessage());
        }
    }
}

很简单:从源文件按块读取,然后立刻写入新文件。该方式适用于任意文件:图片、压缩包、视频等。

3. 写入二进制文件

写入字节数组

如果你有一个字节数组(例如从网络获得或在程序中生成),可以像这样把它写入文件:

byte[] data = new byte[] {1, 2, 3, 4, 5}; // 示例数组

try (FileOutputStream out = new FileOutputStream("data.bin")) {
    out.write(data); // 将整个数组写入文件
}

方法 write(byte[]) 会把数组中的所有字节写入文件。也可以只写一部分:out.write(data, offset, length)。

分段写入文件(例如用于复制)

与读取类似,通常会使用缓冲区:

try (FileInputStream in = new FileInputStream("source.bin");
     FileOutputStream out = new FileOutputStream("dest.bin")) {

    byte[] buffer = new byte[4096];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}

这里把从文件读取到的内容立即写入另一个文件。这类代码在压缩程序、下载器、图像处理器等中很常见。

4. 实用细节

为什么不能对二进制文件使用 Reader/Writer?

Reader 和 Writer 处理的是字符(char),而不是字节。它们会按照某种编码(例如 UTF-8)自动在字节与字符之间转换。这对文本很方便,但对二进制文件则是致命的!

如果你尝试通过 FileWriter 写一张图片,会得到一个损坏且无法打开的文件。请记住:对任何非文本文件,只使用 InputStream/OutputStream!

处理二进制文件的重要区别与注意事项

  • 缓冲区大小:缓冲区过小会变慢(磁盘访问过多),过大则会占用额外内存。4–16 KB 通常较为合适。
  • 错误处理:务必处理 IOException —— 文件可能不存在、被锁定,或磁盘空间不足。
  • 关闭流:使用 try-with-resources —— 即使出错也能确保文件被关闭。
  • 覆盖文件:如果通过 new FileOutputStream("file.bin") 打开文件,它会被覆盖。若要追加到末尾,请使用带有 append = true 参数的构造函数。
  • 访问权限:如果程序无法打开文件,请检查读/写权限。
  • readAllBytes():可一行代码将整个文件读入字节数组。不要对大文件使用,以免耗尽内存!

5. 处理二进制文件的常见错误

错误 №1:对二进制文件使用 FileReader/FileWriter。这会导致数据损坏,因为这些类会在字节与字符之间转换,而对图片、压缩包等来说这是灾难性的。

错误 №2:忽略 read() 的返回值。方法 read(byte[]) 可能读取的字节数少于你请求的,尤其是在最后一个块。务必使用返回值来知道实际处理了多少字节。

错误 №3:忘记关闭流。如果不关闭文件,它可能会一直被锁定,且数据(尤其在写入时)可能无法完全写出。请使用 try-with-resources。

错误 №4:不考虑大小就把整个文件一次性读入内存。对于大文件,这会导致 OutOfMemoryError。请使用缓冲区分块读取。

错误 №5:不处理异常。文件操作随时可能失败:文件未找到、无权限、磁盘已满。别忘了处理 IOException。

1
任务
JAVA 25 SELF, 第 36 级, 课程 2
已锁定
从二进制流中提取有价值片段 🧪
从二进制流中提取有价值片段 🧪
1
任务
JAVA 25 SELF, 第 36 级, 课程 2
已锁定
计算文物的能量潜力 ⚛️
计算文物的能量潜力 ⚛️
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION