源本科技 | 码上会

Java 字节输入流

2026/01/30
43
0

学习目标

  • 理解 FileInputStream 的定位与适用场景

  • 掌握三种构造方式及核心读取方法

  • 能够正确读取二进制文件(如图片、音频)

  • 了解 available()skip() 等辅助方法的作用

  • 学会安全地管理文件流资源


什么是 FileInputStream

FileInputStream 是 Java 中用于从文件读取原始字节数据的输入流,继承自 InputStream。它是处理二进制文件(如图片、音频、视频、压缩包)的标准工具。

重要提示:虽然可以用它读取文本文件,但不推荐!应使用 FileReaderInputStreamReader(可指定编码),避免乱码问题。

核心特性

  • 字节级操作:每次读取 1 个或多个 byte(8 位)

  • 无缓冲:直接从磁盘读取,频繁小量读取性能较差

  • 平台无关:自动适配不同操作系统的文件系统

  • 支持 NIO:可通过 getChannel() 获取 FileChannel 进行高效 I/O


类声明

public class FileInputStream extends InputStream
  • 继承 InputStream:获得 read()close() 等基础方法

  • 实现底层文件读取:将操作系统文件句柄封装为 Java 流


构造器

FileInputStream 提供三种构造方式:

构造器

说明

示例

FileInputStream(String name)

通过文件路径字符串创建

new FileInputStream("data.bin")

FileInputStream(File file)

通过 File 对象创建

new FileInputStream(new File("image.jpg"))

FileInputStream(FileDescriptor fd)

通过文件描述符创建(高级用法)

new FileInputStream(FileDescriptor.in)

// 方式1:直接传路径
FileInputStream fis1 = new FileInputStream("config.properties");

// 方式2:先有 File 对象
File file = new File("logs/app.log");
FileInputStream fis2 = new FileInputStream(file);

// 方式3:从标准输入(极少使用)
FileInputStream fis3 = new FileInputStream(FileDescriptor.in);

推荐:优先使用 StringFile 构造器,清晰且安全


核心方法

方法

返回值

说明

int read()

int

读取单个字节(0~255),末尾返回 -1

int read(byte[] b)

int

读取最多 b.length 字节到数组,返回实际读取数

int read(byte[] b, int off, int len)

int

从偏移 off 开始,最多读 len 字节

long skip(long n)

long

跳过 n 个字节(可能少于 n

int available()

int

估算剩余可读字节数(不精确!)

void close()

void

关闭流并释放系统资源

FileChannel getChannel()

FileChannel

获取关联的 NIO 通道

FileDescriptor getFD()

FileDescriptor

获取底层文件描述符


基础示例

逐字节读取文件

以下程序演示如何读取文本文件(仅作演示,实际应使用字符流):

import java.io.*;

public class FileInputStreamExample {
    public static void main(String[] args) {
        // 确保 file1.txt 存在,内容如:
        // Hello Java FileInputStream!

        try (FileInputStream fis = new FileInputStream("file1.txt")) {
            System.out.println("文件通道: " + fis.getChannel());
            System.out.println("文件描述符: " + fis.getFD());
            System.out.println("预估剩余字节数: " + fis.available());
            
            // 跳过前4个字节("Hell")
            fis.skip(4);
            
            System.out.println("\n文件内容(跳过4字节后):");
            int byteData;
            while ((byteData = fis.read()) != -1) {
                // 将 byte 转为 char 输出(仅适用于ASCII文本!)
                System.out.print((char) byteData);
            }
        } catch (FileNotFoundException e) {
            System.err.println("文件未找到: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("读取错误: " + e.getMessage());
        }
    }
}

假设 file1.txt 内容为:

Hello Java FileInputStream!

输出:

文件通道: sun.nio.ch.FileChannelImpl@...
文件描述符: java.io.FileDescriptor@...
预估剩余字节数: 26

文件内容(跳过4字节后):
o Java FileInputStream!

注意:(char)byteData 仅对 ASCII 文本有效!非 ASCII 字符(如中文)会乱码


图片复制

FileInputStream 的真正价值在于处理二进制数据。以下程序复制一张图片:

import java.io.*;

public class ImageCopy {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("source.jpg");
             FileOutputStream fos = new FileOutputStream("copy.jpg")) {
            
            byte[] buffer = new byte[1024]; // 1KB 缓冲区
            int bytesRead;
            
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead); // 写入实际读取的字节数
            }
            
            System.out.println("图片复制完成!");
        } catch (IOException e) {
            System.err.println("文件操作失败: " + e.getMessage());
        }
    }
}

使用缓冲区(byte[])大幅提升性能,避免逐字节 I/O


最佳实践

  1. 用途明确:仅用于二进制文件读取;文本文件用 FileReader/InputStreamReader

  2. 必须关闭:使用 try-with-resources 自动释放资源

  3. 避免逐字节读:使用 byte[] 缓冲区或 BufferedInputStream

  4. 不要依赖 available():以 read() == -1 判断结束

  5. 大文件处理:考虑 FileChannel + 内存映射提升性能

  6. 异常处理:捕获 FileNotFoundExceptionIOException


重点总结

  • FileInputStream 是读取原始字节的标准方式,适用于二进制文件

  • 三种构造器:StringFileFileDescriptor

  • 核心方法:read()(单字节 / 数组)、skip()available()(估算)

  • 无内置缓冲,性能敏感场景需包装 BufferedInputStream

  • 通过 getChannel() 支持 NIO 高级功能

  • 永远不要用它直接读取非 ASCII 文本(会乱码!)


思考题

  1. 为什么用 FileInputStream 读取包含中文的文本文件会出现乱码?如何正确读取?

  2. fis.read(buffer) 返回值的意义是什么?为什么不能假设它总是等于 buffer.length

  3. 在什么场景下应该直接使用 FileInputStream,什么场景下应包装 BufferedInputStream?性能差异有多大?