核心概念:InputStream 及其子类
InputStream 本身是一个抽象类,我们不能直接实例化它,对于文件操作,我们通常会使用它的具体子类。

FileInputStream
这是最直接、最常用的子类,用于从文件系统中的某个文件中读取字节。
BufferedInputStream
这是一个装饰器类(Decorator),它不直接与文件交互,而是包装一个其他的 InputStream(FileInputStream),为其提供一个内部缓冲区,缓冲可以大大减少直接访问磁盘的次数,从而显著提高读取性能。强烈建议在文件读取时使用它。
基本用法:读取文件内容
我们将通过几个例子来演示如何使用 InputStream 读取文件,假设我们有一个名为 test.txt 的文件,内容为 "Hello, World!"。
示例 1:使用 FileInputStream(不推荐,仅作演示)
这种方式简单直接,但效率较低,因为它每次读取一个字节都可能访问磁盘。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileInputStreamExample {
public static void main(String[] args) {
// try-with-resources 语句,确保流在完成后自动关闭
try (InputStream inputStream = new FileInputStream("test.txt")) {
int byteData;
// read() 方法读取一个字节,返回 int (0-255),
// 如果到达文件末尾,则返回 -1
while ((byteData = inputStream.read()) != -1) {
// 将读取到的字节转换为字符并打印
System.out.print((char) byteData);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
示例 2:使用 BufferedInputStream(推荐)
这是更高效、更常见的做法,我们先用 FileInputStream 打开文件,然后用 BufferedInputStream 包装它。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class BufferedInputStreamExample {
public static void main(String[] args) {
try (InputStream fileInputStream = new FileInputStream("test.txt");
// 用 BufferedInputStream 包装 FileInputStream
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)) {
int byteData;
while ((byteData = bufferedInputStream.read()) != -1) {
System.out.print((char) byteData);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意:try-with-resources 语句会按照声明的逆序自动关闭资源。bufferedInputStream 会先被关闭,然后是 fileInputStream。
高级用法:读取字节数组
逐字节读取对于大文件来说效率依然不高,更常见的方式是读取到一个字节数组中。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ReadByteArrayExample {
public static void main(String[] args) {
// 定义一个 1024 字节的缓冲区
byte[] buffer = new byte[1024];
int bytesRead;
try (InputStream inputStream = new BufferedInputStream(new FileInputStream("test.txt"))) {
// read(byte[] b) 方法尝试将数据读入字节数组 b,
// 返回实际读取的字节数,如果到达文件末尾,返回 -1。
while ((bytesRead = inputStream.read(buffer)) != -1) {
// 将读取到的字节转换为字符串并打印
// 注意:使用 String 构造函数时,指定偏移量和长度,避免打印出缓冲区中未填充的旧数据
System.out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这种方法比逐字节读取快得多,因为它每次可以从磁盘读取一大块数据到内存中。

最佳实践
-
始终使用
try-with-resources:从 Java 7 开始,这是处理InputStream、OutputStream等资源的标准方式,它可以确保资源在任何情况下(无论是正常结束还是发生异常)都会被正确关闭,避免了资源泄漏。 -
优先使用缓冲流:对于文件、网络等 I/O 操作,始终使用
BufferedInputStream和BufferedOutputStream来包装原始流,这是提升性能最简单有效的方法。 -
指定正确的字符编码:
InputStream读取的是原始字节,如果你需要将字节转换为字符串(例如读取文本文件),必须指定正确的字符编码(如 UTF-8),否则可能会出现乱码,直接将字节强制转换为char是不正确的,因为一个字符可能由多个字节组成。// 错误的转换方式 // String str = new String(buffer); // 如果文件编码不是平台默认编码,会乱码 // 正确的转换方式 String str = new String(buffer, "UTF-8");
-
处理大文件:对于非常大的文件(如 GB 级别),不要一次性将所有内容读入内存,应该采用循环读取的方式,每次处理一个缓冲块的数据。
处理不同字符编码的文本文件
虽然 InputStream 是字节流,但我们通常用它来读取文本,为了正确处理字符,Java 提供了 Reader 体系,特别是 InputStreamReader,它可以在 InputStream 和 Reader 之间架起一座桥梁。
InputStreamReader 可以将字节流按照指定的字符编码转换为字符流。
import java.io.*;
import java.nio.charset.StandardCharsets; // 推荐使用 StandardCharsets
public class ReadTextWithEncodingExample {
public static void main(String[] args) {
// 文件路径
String filePath = "test_utf8.txt"; // 假设这是一个 UTF-8 编码的文件
try (
// 1. 创建字节输入流
InputStream inputStream = new FileInputStream(filePath);
// 2. 使用 InputStreamReader 将字节流转换为字符流,并指定编码
Reader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
// 3. 使用 BufferedReader 进行高效读取(装饰 Reader)
BufferedReader bufferedReader = new BufferedReader(inputStreamReader)
) {
String line;
// 4. 按行读取,直到文件末尾
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
InputStream:处理字节,适合处理二进制文件(如图片、视频、音频)。Reader:处理字符,适合处理文本文件。InputStreamReader:是连接InputStream和Reader的桥梁,用于按指定编码读取文本。
现代替代方案:java.nio.file (NIO.2)
从 Java 7 开始,Java 引入了新的 I/O API,位于 java.nio.file 包中,NIO.2 提供了更强大、更灵活的文件操作能力,通常被认为是比传统 I/O 更好的选择。
使用 Files.newInputStream()
Files 类提供了很多静态工具方法,可以更方便地获取 InputStream。
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class NioInputStreamExample {
public static void main(String[] args) {
Path path = Paths.get("test.txt");
try (InputStream inputStream = Files.newInputStream(path)) {
// 使用 NIO.2 的方式读取
byte[] allBytes = Files.readAllBytes(path); // 如果文件不大,可以一次性读取
System.out.println(new String(allBytes, StandardCharsets.UTF_8));
// 或者使用流式处理
// ... 这里可以结合上面的字节读取或 BufferedReader 逻辑 ...
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用 Files.lines() (读取文本行的最佳方式)
如果你只想读取文本文件的每一行,Files.lines() 是最简洁、最高效的方式,它返回一个 Stream<String>,可以方便地进行流式操作。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class NioLinesExample {
public static void main(String[] args) {
Path path = Paths.get("test.txt");
try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
// 使用流式操作,例如打印每一行
lines.forEach(System.out::println);
// 或者 