Skip to content

FileInputStream:直连磁盘的「原始接口」

磁盘上存的是什么?是字节。图片、视频、PDF、压缩包——不管什么文件,底层都是一串字节。

FileInputStream 就是 Java 读写这些字节的「原始接口」,直接连通文件系统,不做任何加工。

三种构造方式

java
// 方式 1:字符串路径(最直接)
FileInputStream fis = new FileInputStream("data.bin");

// 方式 2:File 对象(可以先检查)
File file = new File("data.bin");
if (file.exists() && file.isFile()) {
    FileInputStream fis = new FileInputStream(file);
}

// 方式 3:FileDescriptor(标准输入)
FileInputStream fis = new FileInputStream(FileDescriptor.in);

方式 2 适合需要先判断文件存在性的场景,但注意:FileInputStream 构造函数本身也会在文件不存在时抛 FileNotFoundException,所以检查和构造要放一起。

读取方式:按需选择

单字节读取(慢,测试用)

java
try (FileInputStream fis = new FileInputStream("data.txt")) {
    int b;
    while ((b = fis.read()) != -1) {
        System.out.print((char) b);
    }
}

每次 read() 返回一个字节(0~255)或 -1(流结束)。读 1MB 文件触发约 100 万次系统调用,仅适合小文件或测试场景

批量读取(推荐)

java
try (FileInputStream fis = new FileInputStream("data.txt")) {
    byte[] buffer = new byte[8192];
    int len;
    while ((len = fis.read(buffer)) != -1) {
        // 处理 buffer[0] ~ buffer[len-1]
        System.out.write(buffer, 0, len);  // 直接输出到控制台
    }
}

一次读 8KB,1MB 文件只需 128 次系统调用,这是标准用法

预分配数组(适合小文件)

java
try (FileInputStream fis = new FileInputStream("small.dat")) {
    byte[] buffer = new byte[fis.available()];
    fis.read(buffer);
    // 一次性读完
}

available() 返回「无需阻塞可读取的字节数估计值」,对小文件基本准确。但注意:大文件用它可能预分配过大内存。

完整示例:读取图片到字节数组

java
public byte[] readImageBytes(String path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path)) {
        // 先读文件大小
        int size = fis.available();
        byte[] data = new byte[size];
        fis.read(data);
        return data;
    }
}

public void processBinaryFile(String path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path)) {
        byte[] buffer = new byte[8192];
        int len;
        while ((len = fis.read(buffer)) != -1) {
            processBytes(buffer, len);
        }
    }
}

配套使用:BufferedInputStream

FileInputStream 是节点流,每次 read() 都可能触发系统调用。加上缓冲层

java
try (
    BufferedInputStream bis = new BufferedInputStream(
        new FileInputStream("big.dat"), 8192)
) {
    byte[] buffer = new byte[8192];
    while (bis.read(buffer) != -1) {
        process(buffer);
    }
}

性能差异:1GB 文件从数分钟降到几秒。

mark() / reset():偷看与回退

FileInputStream 支持 mark/reset,可以在读取过程中标记位置,之后再回到那里:

java
try (FileInputStream fis = new FileInputStream("data.bin")) {
    fis.mark(100);  // 标记当前位置,后续最多回退 100 字节

    fis.read(); fis.read(); fis.read();  // 读 3 字节

    fis.reset();  // 回到标记位置

    fis.read();  // 重新读第一个字节
}

避坑指南

编码问题

FileInputStream 只管字节,不管字符。读中文文本:

java
// ❌ 错:返回的是 UTF-8 字节,不是字符
FileInputStream fis = new FileInputStream("utf8.txt");
int b = fis.read();  // 返回字节值,不是 Unicode 字符

// ✅ 对:用 InputStreamReader 指定编码
BufferedReader reader = new BufferedReader(
    new InputStreamReader(
        new FileInputStream("utf8.txt"), StandardCharsets.UTF_8));

文件不存在

java
// ❌ 文件不存在直接抛异常
new FileInputStream("not-exist.txt");

// ✅ 先检查
File f = new File("not-exist.txt");
if (f.exists() && f.isFile()) {
    fis = new FileInputStream(f);
}

一句话总结FileInputStream 是字节输入流的根基,但永远用它包装 BufferedInputStream,永远用批量读取而非单字节。

基于 VitePress 构建