Java InputStream 和 OutputStream 实现类详解
系统讲解了 Java I/O 中 InputStream 和 OutputStream 的核心实现类。内容涵盖文件流、缓冲流、内存流、对象序列化、数据流、管道流、压缩流等分类,详细说明了各流的构造方法、核心 API、使用示例及性能注意事项。同时提供了组合流模式与最佳实践建议,帮助开发者根据场景选择合适流并优化读写性能。

系统讲解了 Java I/O 中 InputStream 和 OutputStream 的核心实现类。内容涵盖文件流、缓冲流、内存流、对象序列化、数据流、管道流、压缩流等分类,详细说明了各流的构造方法、核心 API、使用示例及性能注意事项。同时提供了组合流模式与最佳实践建议,帮助开发者根据场景选择合适流并优化读写性能。


Java 的 I/O(输入/输出)是通过流(Stream)实现的,流是一组有序的数据序列。根据数据的流向,流可以分为输入流(InputStream)和输出流(OutputStream)。字节流以字节(8 位二进制)为单位处理数据,可以操作任何类型的文件,包括文本、图片、音频、视频等二进制文件。
InputStream和OutputStream是 Java 字节流的两个抽象基类,位于java.io包中。它们定义了字节流读写的基本方法,拥有众多的实现类,每个实现类都针对特定的数据源或处理需求进行了优化。本文将详细介绍这些实现类的特点、使用方法和最佳实践。
FileInputStream和FileOutputStream是最基础的字节流实现类,用于从文件中读取字节数据或将字节数据写入文件。它们是 Java 程序与文件系统交互的最直接方式。
FileInputStream 构造方法:
// 通过文件路径创建
FileInputStream fis = new FileInputStream(String name);
// 通过 File 对象创建
FileInputStream fis = new FileInputStream(File file);
// 通过 FileDescriptor 创建
FileInputStream fis = new FileInputStream(FileDescriptor fdObj);
FileOutputStream 构造方法:
// 通过文件路径创建(覆盖模式)
FileOutputStream fos = new FileOutputStream(String name);
// 通过文件路径创建(可指定追加模式)
FileOutputStream fos = new FileOutputStream(String name, boolean append);
// 通过 File 对象创建(覆盖模式)
FileOutputStream fos = new FileOutputStream(File file);
// 通过 File 对象创建(可指定追加模式)
FileOutputStream fos = new FileOutputStream(File file, boolean append);
FileInputStream 主要方法:
int read(): 读取一个字节,返回 0-255 的字节值,到达文件末尾返回 -1int read(byte[] b): 读取最多 b.length 字节到数组中,返回实际读取的字节数int read(byte[] b, int off, int len): 读取最多 len 字节到数组的指定位置long skip(long n): 跳过并丢弃 n 个字节int available(): 返回可读取的字节数估计值void close(): 关闭流并释放相关资源FileOutputStream 主要方法:
void write(int b): 写入一个字节void write(byte[] b): 写入整个字节数组void write(byte[] b, int off, int len): 写入字节数组的一部分void flush(): 刷新缓冲区(对于 FileOutputStream,write 方法直接写入文件,flush 通常不做任何操作)void close(): 关闭流并释放资源基本文件复制示例:
public class FileCopyExample {
public static void main(String[] args) {
// 使用 try-with-resources 自动关闭资源(JDK 7+)
try (FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("dest.jpg")) {
byte[] buffer = new byte[8192]; // 8KB 缓冲区
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
System.out.println("文件复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
追加模式写入示例:
public class AppendToFileExample {
public static void main(String[] args) {
String logEntry = "[" + new Date() + "] 用户登录系统\n";
try (FileOutputStream fos = new FileOutputStream("app.log", true)) {
fos.write(logEntry.getBytes(StandardCharsets.UTF_8));
System.out.println("日志写入成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
read()和write(int))效率极低,每次调用都会触发底层系统调用FileInputStream实际通过 JNI(Java Native Interface)调用操作系统底层的文件读取 API。在 Windows 上是ReadFile函数,在 Linux/Unix 上是read系统调用。因此,频繁的单字节读写会频繁陷入内核态,造成性能瓶颈。
BufferedInputStream和BufferedOutputStream是装饰器模式的典型应用,它们为现有的输入输出流添加缓冲功能,通过减少实际的 I/O 操作次数来显著提升性能。
BufferedInputStream内部维护一个字节数组作为缓冲区。当调用read()方法时,它会尽可能多地读取数据填充缓冲区,后续的读取操作直接从缓冲区返回数据,只有当缓冲区数据耗尽时才再次从底层输入流读取。
BufferedOutputStream同样维护一个字节数组作为缓冲区。写入的数据首先存入缓冲区,当缓冲区满或调用flush()方法时,才将缓冲区数据一次性写入底层输出流。
// 使用默认缓冲区大小(8192 字节)
BufferedInputStream bis = new BufferedInputStream(InputStream in);
BufferedOutputStream bos = new BufferedOutputStream(OutputStream out);
// 指定缓冲区大小
BufferedInputStream bis = new BufferedInputStream(InputStream in, int size);
BufferedOutputStream bos = new BufferedOutputStream(OutputStream out, int size);
public class BufferedStreamPerformance {
public static void main(String[] args) {
String fileName = "test.dat";
// 测试无缓冲写入
long start = System.nanoTime();
try (FileOutputStream fos = new FileOutputStream(fileName)) {
for (int i = 0; i < 1000000; i++) {
fos.write(i); // 单字节写入,每次都会触发系统调用
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.nanoTime();
System.out.println("无缓冲写入时间:" + (end - start) / 1_000_000 + " ms");
// 测试缓冲写入
start = System.nanoTime();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileName))) {
for (int i = 0; i < 1000000; i++) {
bos.write(i); // 写入到缓冲区,满 8K 时才真正写入
}
} catch (IOException e) {
e.printStackTrace();
}
end = System.nanoTime();
System.out.println("缓冲写入时间:" + (end - start) / 1_000_000 + " ms");
// 清理
new File(fileName).delete();
}
}
运行结果通常显示缓冲流比无缓冲流快几十甚至上百倍。
关于 flush() 方法:
BufferedOutputStream的flush()方法强制将缓冲区中的数据写入底层输出流。在关闭流之前,close()方法会自动调用flush()。但在以下情况需要手动调用:
关于 mark/reset 功能:
BufferedInputStream支持mark()和reset()方法,允许读取位置回退。默认情况下 mark 有效,但如果在标记后读取的字节数超过缓冲区大小,标记可能会失效。可以通过构造函数指定更大的缓冲区来避免这个问题。
public class MarkResetExample {
public static void main(String[] args) throws IOException {
byte[] data = "Hello World".getBytes();
try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data))) {
// 标记当前位置(起始位置)
bis.mark(10); // 参数表示标记有效期内最多可以读取的字节数
// 读取前 5 个字节
byte[] buffer = new byte[5];
bis.read(buffer);
System.out.println("第一次读取:" + new String(buffer));
// 重置到标记位置
bis.reset();
// 重新读取
bis.read(buffer);
System.out.println("重置后读取:" + new String(buffer));
}
}
}
默认的 8192 字节(8KB)是经过实践检验的较优值,与文件系统的块大小和 CPU 缓存行大小相匹配。对于大文件顺序读写,可以适当增大缓冲区(如 64KB 或 256KB)来进一步提升性能。但过大的缓冲区可能导致内存浪费和缓存利用率下降。
ByteArrayInputStream和ByteArrayOutputStream是操作内存中字节数组的流实现类。它们不涉及磁盘或网络 I/O,所有操作都在内存中进行,因此速度极快,适合临时数据处理。
close()方法是空实现,调用没有实际效果ByteArrayOutputStream在写入数据时会自动扩容构造方法:
// 使用整个字节数组作为数据源
ByteArrayInputStream bais = new ByteArrayInputStream(byte[] buf);
// 使用字节数组的一部分作为数据源
ByteArrayInputStream bais = new ByteArrayInputStream(byte[] buf, int offset, int length);
使用示例:
public class ByteArrayInputStreamExample {
public static void main(String[] args) {
byte[] data = "Java I/O 内存操作流示例".getBytes(StandardCharsets.UTF_8);
try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) {
byte[] buffer = new byte[1024];
int bytesRead = bais.read(buffer);
String result = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
System.out.println("读取的内容:" + result);
// mark/reset 支持(基于数组,完全可用)
bais.reset(); // 重置到流的开头
System.out.println("重置后可用字节数:" + bais.available());
// 跳过前 5 个字节
bais.skip(5);
byte[] remaining = bais.readAllBytes();
System.out.println("跳过 5 字节后:" + new String(remaining, StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
}
}
构造方法:
// 默认缓冲区大小 32 字节
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 指定初始缓冲区大小
ByteArrayOutputStream baos = new ByteArrayOutputStream(int size);
核心方法:
// 写入数据
void write(int b)
void write(byte[] b, int off, int len)
// 获取数据
byte[] toByteArray()
String toString()
String toString(String charsetName)
// 写入到另一个输出流
void writeTo(OutputStream out)
// 重置(清空缓冲区)
void reset()
// 当前缓冲区大小
int size()
综合使用示例:
public class ByteArrayOutputStreamExample {
public static void main(String[] args) {
// 场景 1:合并多个数据源
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
// 写入不同类型的数据
baos.write("用户名:".getBytes(StandardCharsets.UTF_8));
baos.write("张三\n".getBytes(StandardCharsets.UTF_8));
baos.write("年龄:".getBytes(StandardCharsets.UTF_8));
baos.write(String.valueOf(25).getBytes(StandardCharsets.UTF_8));
// 获取合并后的数据
byte[] combinedData = baos.toByteArray();
System.out.println("合并结果:\n" + new String(combinedData, StandardCharsets.UTF_8));
// 场景 2:写入到文件
try (FileOutputStream fos = new FileOutputStream("user.txt")) {
baos.writeTo(fos);
}
// 场景 3:重用同一个流
baos.reset();
baos.write("新的数据".getBytes(StandardCharsets.UTF_8));
System.out.println("重置后:" + baos.toString(StandardCharsets.UTF_8.name()));
} catch (IOException e) {
e.printStackTrace();
}
// 场景 4:作为临时缓冲区处理网络数据
byte[] networkData = simulateNetworkReceive();
ByteArrayInputStream bais = new ByteArrayInputStream(networkData);
processData(bais);
}
private static void processData(InputStream input) {
// 处理输入流数据
// 这里可以是解析协议、解压缩等操作
}
private static byte[] simulateNetworkReceive() {
return "模拟的网络数据包".getBytes(StandardCharsets.UTF_8);
}
}
除了字节数组流,Java 还提供了字符数组流和字符串流:
这些流在处理文本数据时更加方便,但本质上是为字符流设计的。
ObjectInputStream和ObjectOutputStream是用于对象序列化的高级流,可以将 Java 对象转换为字节序列(序列化),或者将字节序列恢复为 Java 对象(反序列化)。
java.io.Serializable接口(标记接口,无方法需要实现)private static final long serialVersionUID,用于版本控制// 需要包装另一个输出流
ObjectOutputStream oos = new ObjectOutputStream(OutputStream out);
// 需要包装另一个输入流
ObjectInputStream ois = new ObjectInputStream(InputStream in);
ObjectOutputStream:
// 写入对象
void writeObject(Object obj)
// 写入基本类型(实现了 DataOutput 接口)
void writeInt(int v)
void writeBoolean(boolean v)
void writeUTF(String str)
// 写入字符串(Modified UTF-8 格式)
// 刷新(确保所有数据写入底层流)
void flush()
// 重置(清除已缓存的对象引用)
void reset()
ObjectInputStream:
// 读取对象
Object readObject()
// 读取基本类型
int readInt()
boolean readBoolean()
String readUTF()
基本序列化示例:
import java.io.*;
import java.time.LocalDateTime;
// 可序列化的用户类
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String password;
// 敏感信息
private transient String token; // 瞬态字段不序列化
private LocalDateTime loginTime;
public User(String username, String password) {
this.username = username;
this.password = password;
this.loginTime = LocalDateTime.now();
}
@Override
public String toString() {
return "User{username='" + username + "', password='" + password + "', token='" + token + "', loginTime=" + loginTime + '"';
}
// getters and setters
public void setToken(String token) {
this.token = token;
}
}
public class SerializationExample {
public static void main(String[] args) {
String fileName = "user.ser";
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) {
User user = new User("张三", "password123");
user.setToken("auth-token-xyz");
oos.writeObject(user);
System.out.println("对象序列化完成:" + user);
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) {
User deserializedUser = (User) ois.readObject();
System.out.println("对象反序列化完成:" + deserializedUser);
// token 字段为 null,因为它被标记为 transient
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
类可以定义以下方法来自定义序列化行为:
private void writeObject(ObjectOutputStream oos) throws IOException {
// 自定义写入逻辑
oos.defaultWriteObject(); // 写入默认字段
// 写入额外的数据
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// 自定义读取逻辑
ois.defaultReadObject(); // 读取默认字段
// 读取额外的数据并初始化
}
private void readObjectNoData() throws ObjectStreamException {
// 当序列化流缺少该类的数据时调用
}
DataInputStream和DataOutputStream提供了读写 Java 基本数据类型(int、long、float、double 等)和字符串的方法,使得处理二进制数据更加方便。
// 包装另一个输入流
DataInputStream dis = new DataInputStream(InputStream in);
// 包装另一个输出流
DataOutputStream dos = new DataOutputStream(OutputStream out);
DataOutputStream(实现了 DataOutput 接口):
// 写入基本类型
void writeBoolean(boolean v)
void writeByte(int v)
void writeShort(int v)
void writeChar(int v)
void writeInt(int v)
void writeLong(long v)
void writeFloat(float v)
void writeDouble(double v)
// 写入字符串
void writeBytes(String s) // 只写入每个字符的低字节
void writeChars(String s) // 写入完整的字符(每个字符 2 字节)
void writeUTF(String str) // 写入 UTF-8 格式字符串(先写入长度,再写入字节)
// 获取写入的字节数
int size()
DataInputStream(实现了 DataInput 接口):
// 读取基本类型
boolean readBoolean()
byte readByte()
short readShort()
char readChar()
int readInt()
long readLong()
float readFloat()
double readDouble()
// 读取字符串
String readUTF()
public class DataStreamExample {
public static void main(String[] args) {
String fileName = "data.bin";
// 写入不同类型的数据
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(fileName))) {
dos.writeInt(100);
dos.writeDouble(3.14159);
dos.writeBoolean(true);
dos.writeUTF("Hello World");
dos.writeChar('A');
System.out.println("总共写入 " + dos.size() + " 字节");
} catch (IOException e) {
e.printStackTrace();
}
// 按相同顺序读取
try (DataInputStream dis = new DataInputStream(new FileInputStream(fileName))) {
int intValue = dis.readInt();
double doubleValue = dis.readDouble();
boolean boolValue = dis.readBoolean();
String strValue = dis.readUTF();
char charValue = dis.readChar();
System.out.println("读取结果:");
System.out.println("int: " + intValue);
System.out.println("double: " + doubleValue);
System.out.println("boolean: " + boolValue);
System.out.println("String: " + strValue);
System.out.println("char: " + charValue);
} catch (IOException e) {
e.printStackTrace();
}
}
}
writeUTF()方法写入的字符串格式有特定结构:
Modified UTF-8 与标准 UTF-8 的区别:
因此,用readUTF()读取时必须使用writeUTF()写入的格式。
PipedInputStream和PipedOutputStream用于在同一 JVM 中的不同线程之间建立通信管道。一个线程通过PipedOutputStream写入数据,另一个线程通过PipedInputStream读取数据。
// 创建未连接的管道
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
// 创建时连接
PipedInputStream pis = new PipedInputStream(PipedOutputStream src);
PipedOutputStream pos = new PipedOutputStream(PipedInputStream snk);
// 指定缓冲区大小
PipedInputStream pis = new PipedInputStream(int pipeSize);
PipedInputStream pis = new PipedOutputStream(PipedOutputStream src, int pipeSize);
// 连接输入流和输出流
pis.connect(PipedOutputStream src);
pos.connect(PipedInputStream snk);
基本管道通信:
public class PipeExample {
public static void main(String[] args) {
try {
// 创建管道流
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
// 写入线程
Thread writerThread = new Thread(() -> {
try {
String[] messages = {"消息 1", "消息 2", "消息 3", "结束"};
for (String msg : messages) {
pos.write(msg.getBytes(StandardCharsets.UTF_8));
pos.write('\n'); // 添加换行符作为分隔符
System.out.println("写入:" + msg);
Thread.sleep(1000); // 模拟延迟
}
pos.close(); // 关闭输出流,告诉读取线程数据结束
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});
// 读取线程
Thread readerThread = new Thread(() -> {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(pis, StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
System.out.println("读取:" + line);
}
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
});
writerThread.start();
readerThread.start();
// 等待线程完成
writerThread.join();
readerThread.join();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
处理管道断开:
public class PipeDisconnectHandling {
public static void main(String[] args) {
try {
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
// 写入少量数据后关闭
Thread writer = new Thread(() -> {
try {
pos.write("部分数据".getBytes());
pos.close(); // 提前关闭
} catch (IOException e) {
e.printStackTrace();
}
});
// 延迟读取
Thread reader = new Thread(() -> {
try {
Thread.sleep(2000); // 等待写入线程结束
byte[] buffer = new byte[1024];
int bytesRead = pis.read(buffer);
if (bytesRead > 0) {
System.out.println("读取到:" + new String(buffer, 0, bytesRead));
}
// 尝试再次读取
bytesRead = pis.read(buffer);
System.out.println("第二次读取结果:" + bytesRead); // 应为 -1
} catch (IOException | InterruptedException e) {
System.err.println("管道错误:" + e.getMessage());
} finally {
try {
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
writer.start();
reader.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
PushbackInputStream是一个特殊的输入流装饰器,允许将读取的字节推回到流中,以便重新读取。这在解析器实现中特别有用,比如需要预读数据来决定下一步操作。
PushbackInputStream内部维护一个回退缓冲区(字节数组)。当调用unread()方法时,数据被写入这个缓冲区。读取操作优先从回退缓冲区读取数据,只有当缓冲区为空时才从底层输入流读取。
// 使用默认回退缓冲区大小(1 字节)
PushbackInputStream pbis = new PushbackInputStream(InputStream in);
// 指定回退缓冲区大小
PushbackInputStream pbis = new PushbackInputStream(InputStream in, int size);
// 推回一个字节(可以多次调用)
void unread(int b) throws IOException
// 推回整个字节数组
void unread(byte[] b) throws IOException
// 推回字节数组的一部分
void unread(byte[] b, int off, int len) throws IOException
// 可用字节数(回退缓冲区中的字节数 + 底层流可用字节数)
int available() throws IOException
简单的解析器示例:
public class PushbackParserExample {
public static void main(String[] args) {
String data = "123+456";
try (PushbackInputStream pbis = new PushbackInputStream(new ByteArrayInputStream(data.getBytes()), 2)) {
// 读取第一个数字
int num1 = readNumber(pbis);
System.out.println("第一个数字:" + num1);
// 预读运算符
int next = pbis.read();
if (next == '+') {
System.out.println("遇到加号运算符");
// 读取第二个数字
int num2 = readNumber(pbis);
System.out.println("第二个数字:" + num2);
System.out.println("结果:" + (num1 + num2));
} else {
// 如果不是运算符,推回流中
pbis.unread(next);
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 读取连续的数字字符,返回整数
private static int readNumber(PushbackInputStream pbis) throws IOException {
StringBuilder sb = new StringBuilder();
int b;
while ((b = pbis.read()) != -1) {
if (b >= '0' && b <= '9') {
sb.append((char) b);
} else {
// 遇到非数字字符,推回流中
pbis.unread(b);
break;
}
}
return sb.length() > 0 ? Integer.parseInt(sb.toString()) : 0;
}
}
复杂的分词器示例:
public class SimpleTokenizer {
private PushbackInputStream input;
private static final int BUFFER_SIZE = 1024;
public SimpleTokenizer(InputStream input) {
this.input = new PushbackInputStream(input, BUFFER_SIZE);
}
public String nextToken() throws IOException {
skipWhitespace();
int b = input.read();
if (b == -1) {
return null; // EOF
}
// 处理不同类型的 token
if (isDigit(b)) {
return readNumber(b);
} else if (isLetter(b)) {
return readWord(b);
} else if (isOperator(b)) {
return readOperator(b);
} else {
// 单个字符 token
return String.valueOf((char) b);
}
}
private String readNumber(int firstDigit) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append((char) firstDigit);
while (true) {
int b = input.read();
if (b == -1 || !isDigit(b)) {
if (b != -1) {
input.unread(b); // 推回非数字字符
}
break;
}
sb.append((char) b);
}
return sb.toString();
}
private String readWord(int firstLetter) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append((char) firstLetter);
while (true) {
int b = input.read();
if (b == -1 || !isLetterOrDigit(b)) {
if (b != -1) {
input.unread(b);
}
break;
}
sb.append((char) b);
}
return sb.toString();
}
private String readOperator(int firstChar) throws IOException {
// 处理多字符运算符,如 ==, !=, <=, >= 等
StringBuilder sb = new StringBuilder();
sb.append((char) firstChar);
int next = input.read();
if (next != -1) {
String possibleOp = sb.toString() + (char) next;
if (isMultiCharOperator(possibleOp)) {
sb.append((char) next);
} else {
input.unread(next);
}
}
return sb.toString();
}
private void skipWhitespace() throws IOException {
int b;
while ((b = input.read()) != -1 && Character.isWhitespace(b)) {
// 跳过空白字符
}
if (b != -1) {
input.unread(b);
}
}
private boolean isDigit(int b) {
return b >= '0' && b <= '9';
}
private boolean isLetter(int b) {
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z');
}
private boolean isLetterOrDigit(int b) {
return isLetter(b) || isDigit(b) || b == '_';
}
private boolean isOperator(int b) {
return b == '+' || b == '-' || b == '*' || b == '/' || b == '=' || b == '<' || b == '>' || b == '!';
}
private boolean isMultiCharOperator(String op) {
return op.equals("==") || op.equals("!=") || op.equals("<=") || op.equals(">=");
}
public static void main(String[] args) {
String code = "if (x <= 100) { result = 42; }";
try (InputStream is = new ByteArrayInputStream(code.getBytes())) {
SimpleTokenizer tokenizer = new SimpleTokenizer(is);
String token;
while ((token = tokenizer.nextToken()) != null) {
System.out.println("Token: " + token);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
IOExceptionPrintStream是一个功能丰富的输出流装饰器,提供了打印各种数据类型的便利方法。最著名的PrintStream实例就是System.out和System.err。
print()和println()方法printf()和format()方法checkError()检查状态// 包装 OutputStream
PrintStream ps = new PrintStream(OutputStream out);
PrintStream ps = new PrintStream(OutputStream out, boolean autoFlush);
PrintStream ps = new PrintStream(OutputStream out, boolean autoFlush, String encoding);
// 直接创建文件流
PrintStream ps = new PrintStream(String fileName);
PrintStream ps = new PrintStream(String fileName, String encoding);
PrintStream ps = new PrintStream(File file);
打印方法:
// 打印不换行
void print(boolean b)
void print(char c)
void print(int i)
void print(long l)
void print(float f)
void print(double d)
void print(char[] s)
void print(String s)
void print(Object obj)
// 打印并换行
void println()
void println(boolean x)
// ... 各种类型的 println 重载
// 格式化输出
PrintStream printf(String format, Object... args)
PrintStream format(String format, Object... args)
错误检查:
// 检查是否发生错误
boolean checkError()
// 清除错误状态(protected 方法)
protected void clearError()
基本输出:
public class PrintStreamBasicExample {
public static void main(String[] args) {
try (PrintStream ps = new PrintStream("output.txt", "UTF-8")) {
// 各种打印方法
ps.print(true);
ps.print(' ');
ps.print(123);
ps.print(' ');
ps.print(3.14159);
ps.println(); // 换行
ps.println("这是一行文本");
ps.println(new Object());
// 格式化输出
ps.printf("姓名:%s,年龄:%d,身高:%.2f 米%n", "张三", 25, 1.75);
// 格式化并返回 PrintStream,支持链式调用
ps.format("圆周率:%.10f%n", Math.PI)
.format("当前时间:%tF %<tT%n", new Date());
// 检查错误状态
if (ps.checkError()) {
System.err.println("写入过程中发生错误");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
重定向 System.out:
public class RedirectSystemOutExample {
public static void main(String[] args) {
// 保存原始 System.out
PrintStream originalOut = System.out;
try {
// 创建文件输出流
PrintStream fileOut = new PrintStream(new FileOutputStream("console.log", true), true, "UTF-8");
// 重定向 System.out
System.setOut(fileOut);
// 这些输出会写入文件
System.out.println("这是写入文件的第一行");
System.out.printf("当前时间:%tF %<tT%n", new Date());
// 创建日志记录
logMessage("系统启动");
logMessage("用户登录");
System.out.println("写入完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 恢复原始 System.out
System.setOut(originalOut);
System.out.println("已恢复控制台输出");
}
}
private static void logMessage(String message) {
// 此时 System.out 已被重定向
System.out.printf("[%tF %<tT] %s%n", new Date(), message);
}
}
自定义日志类:
public class Logger {
private PrintStream out;
private PrintStream err;
private boolean debugEnabled;
public Logger(String logFile) throws IOException {
// 普通日志输出到文件
this.out = new PrintStream(new FileOutputStream(logFile, true), true, "UTF-8");
// 错误日志同时输出到文件和标准错误
this.err = new PrintStream(new TeeOutputStream(System.err, new FileOutputStream(logFile + ".err", true)), true, "UTF-8");
}
public void info(String format, Object... args) {
out.printf("[INFO] " + format + "%n", args);
}
public void error(String format, Object... args) {
err.printf("[ERROR] " + format + "%n", args);
}
public void debug(String format, Object... args) {
if (debugEnabled) {
out.printf("[DEBUG] " + format + "%n", args);
}
}
public void setDebugEnabled(boolean enabled) {
this.debugEnabled = enabled;
}
public void close() {
out.close();
err.close();
}
// 辅助类:将输出同时写入两个流
private static class TeeOutputStream extends OutputStream {
private OutputStream out1;
private OutputStream out2;
public TeeOutputStream(OutputStream out1, OutputStream out2) {
this.out1 = out1;
this.out2 = out2;
}
@Override
public void write(int b) throws IOException {
out1.write(b);
out2.write(b);
}
@Override
public void flush() throws IOException {
out1.flush();
out2.flush();
}
@Override
public void close() throws IOException {
try {
out1.close();
} finally {
out2.close();
}
}
}
}
checkError()检查错误FilterInputStream和FilterOutputStream是装饰器模式中的装饰者基类。它们本身不添加额外功能,只是简单地将所有方法调用转发给底层流。所有装饰器类(如 BufferedInputStream、DataInputStream)都继承自这些过滤流。
// 自定义过滤流:将读取的字节转换为大写
public class UpperCaseInputStream extends FilterInputStream {
protected UpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int b = super.read();
return (b == -1) ? -1 : Character.toUpperCase(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int bytesRead = super.read(b, off, len);
if (bytesRead > 0) {
for (int i = off; i < off + bytesRead; i++) {
b[i] = (byte) Character.toUpperCase(b[i] & 0xFF);
}
}
return bytesRead;
}
}
// 自定义过滤流:计算写入数据的 CRC32 校验和
public class CRCOutputStream extends FilterOutputStream {
private CRC32 crc = new CRC32();
private long bytesWritten = 0;
public CRCOutputStream(OutputStream out) {
super(out);
}
@Override
public void write(int b) throws IOException {
out.write(b);
crc.update(b);
bytesWritten++;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
crc.update(b, off, len);
bytesWritten += len;
}
public long getCRCValue() {
return crc.getValue();
}
public long getBytesWritten() {
return bytesWritten;
}
}
// 使用示例
public class CustomFilterExample {
public static void main(String[] args) {
String data = "hello world";
// 使用 UpperCaseInputStream
try (InputStream is = new UpperCaseInputStream(new ByteArrayInputStream(data.getBytes()))) {
byte[] buffer = new byte[1024];
int bytesRead = is.read(buffer);
System.out.println("大写转换结果:" + new String(buffer, 0, bytesRead));
} catch (IOException e) {
e.printStackTrace();
}
// 使用 CRCOutputStream
try (CRCOutputStream cos = new CRCOutputStream(new FileOutputStream("test.dat"))) {
cos.write(data.getBytes());
cos.write("\n第二行".getBytes());
System.out.println("写入字节数:" + cos.getBytesWritten());
System.out.println("CRC32 校验和:" + Long.toHexString(cos.getCRCValue()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
SequenceInputStream可以将多个输入流连接成一个输入流,按顺序读取每个流,直到所有流都读完。这在处理分段文件、合并多个数据源时非常有用。
// 使用枚举
SequenceInputStream sis = new SequenceInputStream(Enumeration<? extends InputStream> e);
// 使用两个流
SequenceInputStream sis = new SequenceInputStream(InputStream s1, InputStream s2);
public class SequenceInputStreamExample {
public static void main(String[] args) {
// 准备多个数据源
String[] data = {"第一部分数据\n", "第二部分数据\n", "第三部分数据\n"};
List<InputStream> streams = new ArrayList<>();
for (String d : data) {
streams.add(new ByteArrayInputStream(d.getBytes()));
}
// 创建枚举
Enumeration<InputStream> enumeration = Collections.enumeration(streams);
// 合并流
try (SequenceInputStream sis = new SequenceInputStream(enumeration);
FileOutputStream fos = new FileOutputStream("combined.txt")) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = sis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
System.out.println("所有数据已合并到 combined.txt");
} catch (IOException e) {
e.printStackTrace();
}
// 读取合并后的文件验证
try (FileInputStream fis = new FileInputStream("combined.txt")) {
byte[] content = fis.readAllBytes();
System.out.println("合并文件内容:\n" + new String(content));
} catch (IOException e) {
e.printStackTrace();
}
}
}
SequenceInputStream会自动关闭所有包含的输入流CheckedInputStream和CheckedOutputStream是 Java 标准库中用于计算数据校验和的过滤流。它们在读写数据的同时更新校验和,常用于数据完整性验证。
// 需要指定 Checksum 实现
CheckedInputStream cis = new CheckedInputStream(InputStream in, Checksum cksum);
CheckedOutputStream cos = new CheckedOutputStream(OutputStream out, Checksum cksum);
// 获取校验和
Checksum getChecksum()
Checksum接口import java.util.zip.CRC32;
import java.util.zip.Adler32;
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;
public class ChecksumExample {
// 计算文件的 CRC32 校验和
public static long calculateCRC32(String filePath) throws IOException {
try (CheckedInputStream cis = new CheckedInputStream(new FileInputStream(filePath), new CRC32())) {
byte[] buffer = new byte[8192];
while (cis.read(buffer) != -1) { // 持续读取,校验和在内部自动更新
}
return cis.getChecksum().getValue();
}
}
// 复制文件并同时计算校验和
public static long copyWithChecksum(String source, String dest) throws IOException {
try (CheckedInputStream cis = new CheckedInputStream(new FileInputStream(source), new CRC32());
FileOutputStream fos = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = cis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
return cis.getChecksum().getValue();
}
}
// 写入数据并计算校验和
public static void writeWithChecksum(String filePath, byte[] data) throws IOException {
try (CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(filePath), new Adler32())) {
cos.write(data);
cos.flush();
long checksum = cos.getChecksum().getValue();
System.out.printf("写入完成,Adler32 校验和:0x%x%n", checksum);
// 将校验和单独保存
try (FileOutputStream ckFile = new FileOutputStream(filePath + ".cksum")) {
DataOutputStream dos = new DataOutputStream(ckFile);
dos.writeLong(checksum);
}
}
}
public static void main(String[] args) {
try {
String testFile = "testdata.bin";
// 生成测试数据
byte[] data = new byte[1024 * 1024]; // 1MB
new Random().nextBytes(data);
// 写入数据并计算校验和
writeWithChecksum(testFile, data);
// 读取文件并验证校验和
long calculatedCRC = calculateCRC32(testFile);
System.out.printf("读取文件的 CRC32 校验和:0x%x%n", calculatedCRC);
// 复制文件并验证校验和是否一致
String copyFile = "copy_" + testFile;
long copyCRC = copyWithChecksum(testFile, copyFile);
System.out.printf("复制文件的 CRC32 校验和:0x%x%n", copyCRC);
if (calculatedCRC == copyCRC) {
System.out.println("校验和一致,文件复制正确");
} else {
System.out.println("校验和不一致,文件可能损坏");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Java 的java.util.zip包提供了多种压缩和解压缩流,其中最常用的是GZIPInputStream和GZIPOutputStream,它们实现了 GZIP 文件格式的压缩和解压缩。
// 压缩输出流
GZIPOutputStream gzos = new GZIPOutputStream(OutputStream out);
GZIPOutputStream gzos = new GZIPOutputStream(OutputStream out, int size); // 指定缓冲区大小
// 解压缩输入流
GZIPInputStream gzis = new GZIPInputStream(InputStream in);
GZIPInputStream gzis = new GZIPInputStream(InputStream in, int size);
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class GZIPExample {
// 压缩文件
public static void compressFile(String sourceFile, String gzipFile) throws IOException {
try (FileInputStream fis = new FileInputStream(sourceFile);
FileOutputStream fos = new FileOutputStream(gzipFile);
GZIPOutputStream gzos = new GZIPOutputStream(fos)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
gzos.write(buffer, 0, bytesRead);
}
// GZIPOutputStream 需要 finish() 来写入压缩数据尾部
gzos.finish();
System.out.println("文件已压缩:" + gzipFile);
}
}
// 解压缩文件
public static void decompressFile(String gzipFile, String outputFile) throws IOException {
try (GZIPInputStream gzis = new GZIPInputStream(new FileInputStream(gzipFile));
FileOutputStream fos = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = gzis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
System.out.println("文件已解压缩:" + outputFile);
}
}
// 压缩数据流
public static byte[] compressData(byte[] data) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (GZIPOutputStream gzos = new GZIPOutputStream(baos)) {
gzos.write(data);
gzos.finish();
}
return baos.toByteArray();
}
// 解压缩数据流
public static byte[] decompressData(byte[] compressedData) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(compressedData);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (GZIPInputStream gzis = new GZIPInputStream(bais)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = gzis.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
}
return baos.toByteArray();
}
public static void main(String[] args) {
try {
// 创建测试文件
String testFile = "test.txt";
try (FileWriter fw = new FileWriter(testFile)) {
for (int i = 0; i < 10000; i++) {
fw.write("这是一行测试文本,用于演示 GZIP 压缩功能。\n");
}
}
// 压缩文件
String gzipFile = "test.txt.gz";
compressFile(testFile, gzipFile);
// 检查压缩效果
File original = new File(testFile);
File compressed = new File(gzipFile);
System.out.printf("原始大小:%d bytes%n", original.length());
System.out.printf("压缩后大小:%d bytes%n", compressed.length());
System.out.printf("压缩率:%.2f%%%n", (1 - (double) compressed.length() / original.length()) * 100);
// 解压缩文件
String decompressedFile = "decompressed.txt";
decompressFile(gzipFile, decompressedFile);
// 验证解压缩后内容相同
String originalContent = new String(java.nio.file.Files.readAllBytes(original.toPath()));
String decompressedContent = new String(java.nio.file.Files.readAllBytes(new File(decompressedFile).toPath()));
System.out.println("内容相同:" + originalContent.equals(decompressedContent));
} catch (IOException e) {
e.printStackTrace();
}
}
}
ZipInputStream和ZipOutputStream用于处理 ZIP 格式的压缩文件,支持包含多个条目的 ZIP 档案。
import java.util.zip.*;
public class ZipExample {
// 创建 ZIP 文件
public static void createZipFile(String zipFileName, Map<String, byte[]> files) throws IOException {
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFileName))) {
for (Map.Entry<String, byte[]> entry : files.entrySet()) {
// 创建 ZIP 条目
ZipEntry zipEntry = new ZipEntry(entry.getKey());
zos.putNextEntry(zipEntry);
// 写入文件数据
zos.write(entry.getValue());
// 关闭当前条目
zos.closeEntry();
}
System.out.println("ZIP 文件创建完成:" + zipFileName);
}
}
// 读取 ZIP 文件
public static void readZipFile(String zipFileName) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFileName))) {
ZipEntry entry;
byte[] buffer = new byte[8192];
while ((entry = zis.getNextEntry()) != null) {
System.out.println("读取条目:" + entry.getName());
System.out.println(" 大小:" + entry.getSize() + " bytes");
System.out.println(" 压缩后大小:" + entry.getCompressedSize() + " bytes");
System.out.println(" 方法:" + (entry.getMethod() == ZipEntry.DEFLATED ? "DEFLATED" : "STORED"));
// 读取条目内容
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bytesRead;
while ((bytesRead = zis.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
System.out.println(" 内容长度:" + baos.size() + " bytes");
System.out.println();
zis.closeEntry();
}
}
}
// 使用 ZipFile 随机访问 ZIP 条目(推荐用于大量文件)
public static void readWithZipFile(String zipFileName) throws IOException {
try (ZipFile zipFile = new ZipFile(zipFileName)) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
System.out.println("ZIP 条目:" + entry.getName());
// 直接获取输入流读取内容
try (InputStream is = zipFile.getInputStream(entry)) {
byte[] content = is.readAllBytes();
System.out.println(" 内容大小:" + content.length);
}
}
}
}
public static void main(String[] args) {
try {
// 准备要压缩的文件
Map<String, byte[]> files = new HashMap<>();
files.put("document.txt", "这是文档内容".getBytes(StandardCharsets.UTF_8));
files.put("data.bin", new byte[1000]);
files.put("subdir/notes.txt", "这是子目录中的笔记".getBytes(StandardCharsets.UTF_8));
// 创建 ZIP 文件
String zipFile = "archive.zip";
createZipFile(zipFile, files);
// 读取 ZIP 文件
System.out.println("\n使用 ZipInputStream 读取:");
readZipFile(zipFile);
System.out.println("\n使用 ZipFile 读取:");
readWithZipFile(zipFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Java I/O 库广泛使用了装饰器模式(Decorator Pattern),允许动态地为流添加功能。这种设计使得组合不同功能的流变得非常简单。
// 一个典型的组合流示例
InputStream input = new BufferedInputStream(
new GZIPInputStream(
new FileInputStream("data.gz"
)
);
// 基础文件输入
public class StreamCombinations {
// 高效读取压缩的序列化对象
public static Object readCompressedObject(String file) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(
new GZIPInputStream(
new FileInputStream(file)
)
)
)) {
return ois.readObject();
}
}
// 写入带校验和的压缩数据
public static void writeCompressedWithChecksum(String file, byte[] data) throws IOException {
try (CheckedOutputStream cos = new CheckedOutputStream(
new GZIPOutputStream(
new BufferedOutputStream(
new FileOutputStream(file)
)
),
new CRC32()
)) {
cos.write(data);
cos.flush();
System.out.printf("数据 CRC32: 0x%x%n", cos.getChecksum().getValue());
}
}
// 网络数据传输组合
public static void sendObjectOverNetwork(Socket socket, Object obj) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(socket.getOutputStream()))) {
oos.writeObject(obj);
oos.flush();
}
}
// 读取配置文件(使用 PushbackInputStream 处理注释)
public static Properties readConfig(String file) throws IOException {
Properties props = new Properties();
try (PushbackInputStream pbis = new PushbackInputStream(
new BufferedInputStream(
new FileInputStream(file)
), 2)) {
StringBuilder currentLine = new StringBuilder();
int b;
while ((b = pbis.read()) != -1) {
// 检查是否是注释行(以#开头)
if (b == '#') {
// 跳过整行
while ((b = pbis.read()) != -1 && b != '\n') {
// 跳过注释内容
}
continue;
}
// 处理非注释行
currentLine.append((char) b);
// 如果遇到换行符,处理这一行
if (b == '\n') {
String line = currentLine.toString().trim();
if (!line.isEmpty()) {
// 解析属性行
String[] parts = line.split("=", 2);
if (parts.length == 2) {
props.setProperty(parts[0].trim(), parts[1].trim());
}
}
currentLine = new StringBuilder();
}
}
}
return props;
}
}
public class BufferSizeOptimization {
// 根据设备特性选择缓冲区大小
public static int getOptimalBufferSize() {
// 对于 HDD,建议 64KB
// 对于 SSD,建议 16KB-32KB
// 对于网络流,建议 8KB-16KB
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("linux")) {
// Linux 通常使用 4KB 页大小,缓冲区设为 4KB 的倍数
return 8192; // 8KB
} else if (os.contains("win")) {
// Windows 通常使用 64KB 的簇大小
return 65536; // 64KB
} else {
return 16384; // 16KB 默认值
}
}
// 智能缓冲流创建
public static InputStream createOptimizedInputStream(File file) throws IOException {
int bufferSize = getOptimalBufferSize();
return new BufferedInputStream(new FileInputStream(file), bufferSize);
}
}
public class CommonPitfalls {
// 错误:没有使用缓冲
public static void badCopy(File source, File dest) throws IOException {
try (FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(dest)) {
int b;
while ((b = fis.read()) != -1) {
// 单字节读取,性能极差
fos.write(b);
}
}
}
// 正确:使用缓冲
public static void goodCopy(File source, File dest) throws IOException {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
}
}
// 错误:不正确的编码处理
public static void badEncodingHandling(String file) throws IOException {
try (FileInputStream fis = new FileInputStream(file)) {
byte[] data = fis.readAllBytes();
String text = new String(data); // 使用平台默认编码,可能乱码
System.out.println(text);
}
}
// 正确:指定编码
public static void goodEncodingHandling(String file) throws IOException {
try (FileInputStream fis = new FileInputStream(file)) {
byte[] data = fis.readAllBytes();
String text = new String(data, StandardCharsets.UTF_8);
System.out.println(text);
}
}
// 错误:关闭顺序不当
public static void badCloseOrder(String file) throws IOException {
GZIPOutputStream gzos = null;
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
gzos = new GZIPOutputStream(fos);
gzos.write("data".getBytes());
} finally {
gzos.close(); // 先关闭包装流
fos.close(); // 后关闭底层流 - GZIPOutputStream 已经关闭了它
}
}
// 正确:try-with-resources 自动处理
public static void goodCloseOrder(String file) throws IOException {
try (FileOutputStream fos = new FileOutputStream(file);
GZIPOutputStream gzos = new GZIPOutputStream(fos)) {
gzos.write("data".getBytes());
}
// 自动关闭,顺序正确(后创建的先关闭)
}
}
public class ExceptionHandlingBestPractices {
// 完整的异常处理模式
public static void safeFileCopy(File source, File dest) {
// 使用 try-with-resources 自动关闭资源(JDK 7+)
try (InputStream in = new BufferedInputStream(new FileInputStream(source));
OutputStream out = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
out.flush(); // 确保所有数据写入
} catch (FileNotFoundException e) {
System.err.println("文件未找到:" + e.getMessage());
// 根据业务需求处理,可能是创建文件或抛出业务异常
} catch (IOException e) {
System.err.println("I/O 错误:" + e.getMessage());
// 记录日志,可能重试或通知用户
e.printStackTrace();
} catch (SecurityException e) {
System.err.println("没有权限访问文件:" + e.getMessage());
// 权限不足的处理
}
}
// 处理部分写入的情况
public static void robustWrite(File file, byte[] data) throws IOException {
try (FileOutputStream fos = new FileOutputStream(file)) {
int written = 0;
int remaining = data.length;
while (remaining > 0) {
int bytesToWrite = Math.min(remaining, 8192);
fos.write(data, written, bytesToWrite);
written += bytesToWrite;
remaining -= bytesToWrite;
}
System.out.printf("成功写入 %d 字节到 %s%n", written, file);
}
}
// 使用 finally 确保资源关闭(JDK 6 及之前版本)
public static void legacyResourceHandling(String file) {
InputStream in = null;
try {
in = new FileInputStream(file);
// 处理数据
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// 记录关闭异常,但不应掩盖原始异常
e.printStackTrace();
}
}
}
}
}
| 实现类 | 类型 | 主要用途 | 特点 | 适用场景 |
|---|---|---|---|---|
| FileInputStream/FileOutputStream | 节点流 | 文件读写 | 基础文件操作 | 所有文件 I/O 的基础 |
| BufferedInputStream/BufferedOutputStream | 处理流 | 缓冲功能 | 提高性能,减少 I/O 次数 | 所有需要提高性能的场景 |
| ByteArrayInputStream/ByteArrayOutputStream | 节点流 | 内存操作 | 速度快,无需关闭 | 临时数据存储、数据转换 |
| ObjectInputStream/ObjectOutputStream | 处理流 | 对象序列化 | 保存和恢复对象 | 对象持久化、RMI |
| DataInputStream/DataOutputStream | 处理流 | 基本类型读写 | 平台无关的二进制数据 | 网络协议、二进制文件格式 |
| PipedInputStream/PipedOutputStream | 节点流 | 线程间通信 | 阻塞式管道通信 | 生产者 - 消费者模式 |
| PushbackInputStream | 处理流 | 回退功能 | 允许重新读取 | 语法解析、协议解析 |
| PrintStream | 处理流 | 格式化输出 | 方便的打印方法 | 日志输出、System.out |
| SequenceInputStream | 处理流 | 流合并 | 合并多个输入流 | 文件合并、分段数据处理 |
| GZIPInputStream/GZIPOutputStream | 处理流 | 数据压缩 | GZIP 格式压缩 | 文件压缩、网络传输优化 |
| ZipInputStream/ZipOutputStream | 处理流 | ZIP 文件处理 | 支持多条目 | ZIP 档案操作 |
| CheckedInputStream/CheckedOutputStream | 处理流 | 校验和计算 | 数据完整性验证 | 文件传输验证、存储校验 |
根据不同的使用场景,推荐以下选择策略:
文件读写场景:
内存数据处理:
数据持久化:
网络通信:
多线程编程:
特殊需求:
read()/write()都可能触发系统调用read(byte[])和write(byte[])方法Java 的 I/O 体系虽然庞大,但掌握了核心概念和常用实现类后,可以灵活组合出满足各种需求的解决方案。建议:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online