跳到主要内容
Java InputStream 和 OutputStream 实现类详解 | 极客日志
Java java
Java InputStream 和 OutputStream 实现类详解 综述由AI生成 系统讲解了 Java I/O 中 InputStream 和 OutputStream 的核心实现类。内容涵盖文件流、缓冲流、内存流、对象序列化、数据流、管道流、压缩流等分类,详细说明了各流的构造方法、核心 API、使用示例及性能注意事项。同时提供了组合流模式与最佳实践建议,帮助开发者根据场景选择合适流并优化读写性能。
山野诗人 发布于 2026/3/27 更新于 2026/5/30 34 浏览Java InputStream 和 OutputStream 实现类详解
引言:Java I/O 体系概述
Java 的 I/O(输入/输出)是通过流(Stream)实现的,流是一组有序的数据序列。根据数据的流向,流可以分为输入流(InputStream)和输出流(OutputStream)。字节流以字节(8 位二进制)为单位处理数据,可以操作任何类型的文件,包括文本、图片、音频、视频等二进制文件。
InputStream和OutputStream是 Java 字节流的两个抽象基类,位于java.io包中。它们定义了字节流读写的基本方法,拥有众多的实现类,每个实现类都针对特定的数据源或处理需求进行了优化。本文将详细介绍这些实现类的特点、使用方法和最佳实践。
第一部分:文件操作流
1.1 FileInputStream 和 FileOutputStream
1.1.1 基本概念
FileInputStream和FileOutputStream是最基础的字节流实现类,用于从文件中读取字节数据或将字节数据写入文件。它们是 Java 程序与文件系统交互的最直接方式。
1.1.2 构造方法
FileInputStream 构造方法 :
FileInputStream fis = new FileInputStream (String name);
FileInputStream fis = new FileInputStream (File file);
FileInputStream fis = new FileInputStream (FileDescriptor fdObj);
FileOutputStream 构造方法 :
FileOutputStream fos = new FileOutputStream (String name);
(String name, append);
(File file);
(File file, append);
FileOutputStream
fos
=
new
FileOutputStream
boolean
FileOutputStream
fos
=
new
FileOutputStream
FileOutputStream
fos
=
new
FileOutputStream
boolean
1.1.3 核心方法
int read(): 读取一个字节,返回 0-255 的字节值,到达文件末尾返回 -1
int read(byte[] b): 读取最多 b.length 字节到数组中,返回实际读取的字节数
int read(byte[] b, int off, int len): 读取最多 len 字节到数组的指定位置
long skip(long n): 跳过并丢弃 n 个字节
int available(): 返回可读取的字节数估计值
void close(): 关闭流并释放相关资源
void write(int b): 写入一个字节
void write(byte[] b): 写入整个字节数组
void write(byte[] b, int off, int len): 写入字节数组的一部分
void flush(): 刷新缓冲区(对于 FileOutputStream,write 方法直接写入文件,flush 通常不做任何操作)
void close(): 关闭流并释放资源
1.1.4 使用示例 public class FileCopyExample {
public static void main (String[] args) {
try (FileInputStream fis = new FileInputStream ("source.jpg" );
FileOutputStream fos = new FileOutputStream ("dest.jpg" )) {
byte [] buffer = new byte [8192 ];
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();
}
}
}
1.1.5 性能考虑
单字节读写(read()和write(int))效率极低,每次调用都会触发底层系统调用
建议使用缓冲区(字节数组)批量读写,缓冲区大小通常设为 4KB-8KB
对于更高性能需求,可以配合缓冲流使用
1.2 FileInputStream 的内部工作原理 FileInputStream实际通过 JNI(Java Native Interface)调用操作系统底层的文件读取 API。在 Windows 上是ReadFile函数,在 Linux/Unix 上是read系统调用。因此,频繁的单字节读写会频繁陷入内核态,造成性能瓶颈。
第二部分:缓冲流
2.1 BufferedInputStream 和 BufferedOutputStream
2.1.1 基本概念 BufferedInputStream和BufferedOutputStream是装饰器模式 的典型应用,它们为现有的输入输出流添加缓冲功能,通过减少实际的 I/O 操作次数来显著提升性能。
2.1.2 工作原理 BufferedInputStream 内部维护一个字节数组作为缓冲区。当调用read()方法时,它会尽可能多地读取数据填充缓冲区,后续的读取操作直接从缓冲区返回数据,只有当缓冲区数据耗尽时才再次从底层输入流读取。
BufferedOutputStream 同样维护一个字节数组作为缓冲区。写入的数据首先存入缓冲区,当缓冲区满或调用flush()方法时,才将缓冲区数据一次性写入底层输出流。
2.1.3 构造方法
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);
2.1.4 性能对比示例 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);
}
} catch (IOException e) {
e.printStackTrace();
}
end = System.nanoTime();
System.out.println("缓冲写入时间:" + (end - start) / 1_000_000 + " ms" );
new File (fileName).delete();
}
}
运行结果通常显示缓冲流比无缓冲流快几十甚至上百倍。
2.1.5 重要注意事项 关于 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 );
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));
}
}
}
2.2 缓冲流的缓冲区大小选择 默认的 8192 字节(8KB)是经过实践检验的较优值,与文件系统的块大小和 CPU 缓存行大小相匹配。对于大文件顺序读写,可以适当增大缓冲区(如 64KB 或 256KB)来进一步提升性能。但过大的缓冲区可能导致内存浪费和缓存利用率下降。
第三部分:内存操作流
3.1 ByteArrayInputStream 和 ByteArrayOutputStream
3.1.1 基本概念 ByteArrayInputStream和ByteArrayOutputStream是操作内存中字节数组的流实现类。它们不涉及磁盘或网络 I/O,所有操作都在内存中进行,因此速度极快,适合临时数据处理。
3.1.2 特点
无需关闭 :这两个流的close()方法是空实现,调用没有实际效果
内存操作 :数据存储在 JVM 堆内存中
线程不安全 :多个线程同时访问需要外部同步
自动扩容 :ByteArrayOutputStream在写入数据时会自动扩容
3.1.3 ByteArrayInputStream 详解
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);
bais.reset();
System.out.println("重置后可用字节数:" + bais.available());
bais.skip(5 );
byte [] remaining = bais.readAllBytes();
System.out.println("跳过 5 字节后:" + new String (remaining, StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.1.4 ByteArrayOutputStream 详解
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) {
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));
try (FileOutputStream fos = new FileOutputStream ("user.txt" )) {
baos.writeTo(fos);
}
baos.reset();
baos.write("新的数据" .getBytes(StandardCharsets.UTF_8));
System.out.println("重置后:" + baos.toString(StandardCharsets.UTF_8.name()));
} catch (IOException e) {
e.printStackTrace();
}
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);
}
}
3.3 其他内存操作流 除了字节数组流,Java 还提供了字符数组流和字符串流:
CharArrayReader / CharArrayWriter :操作字符数组的字符流
StringReader / StringWriter :操作字符串的字符流
这些流在处理文本数据时更加方便,但本质上是为字符流设计的。
第四部分:对象序列化流
4.1 ObjectInputStream 和 ObjectOutputStream
4.1.1 基本概念 ObjectInputStream和ObjectOutputStream是用于对象序列化的高级流,可以将 Java 对象转换为字节序列(序列化),或者将字节序列恢复为 Java 对象(反序列化)。
4.1.2 序列化要求
对象类必须实现java.io.Serializable接口(标记接口,无方法需要实现)
所有非静态、非瞬态(transient)字段都会被序列化
如果父类没有实现 Serializable,父类必须有默认构造函数
序列化版本 ID:private static final long serialVersionUID,用于版本控制
4.1.3 构造方法
ObjectOutputStream oos = new ObjectOutputStream (OutputStream out);
ObjectInputStream ois = new ObjectInputStream (InputStream in);
4.1.4 核心方法
void writeObject (Object obj)
void writeInt (int v)
void writeBoolean (boolean v)
void writeUTF (String str)
void flush ()
void reset ()
Object readObject ()
int readInt ()
boolean readBoolean ()
String readUTF ()
4.1.5 使用示例 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 + '"' ;
}
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);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
4.1.6 自定义序列化 private void writeObject (ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
}
private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
}
private void readObjectNoData () throws ObjectStreamException {
}
4.1.7 序列化注意事项
性能考虑 :序列化会遍历整个对象图,对大对象图进行序列化可能较慢
安全考虑 :反序列化可能执行恶意代码,始终验证输入来源
版本兼容 :修改类结构可能导致反序列化失败,使用 serialVersionUID 控制兼容性
循环引用 :ObjectOutputStream 可以处理对象间的循环引用
关闭流 :必须确保正确关闭,否则可能数据不完整
第五部分:数据流
5.1 DataInputStream 和 DataOutputStream
5.1.1 基本概念 DataInputStream和DataOutputStream提供了读写 Java 基本数据类型(int、long、float、double 等)和字符串的方法,使得处理二进制数据更加方便。
5.1.2 特点
平台无关 :使用与机器无关的方式读写数据,保证数据在不同平台间可移植
类型安全 :可以直接读写基本类型,无需手动转换字节
网络传输 :常用于网络协议实现和二进制文件格式处理
5.1.3 构造方法
DataInputStream dis = new DataInputStream (InputStream in);
DataOutputStream dos = new DataOutputStream (OutputStream out);
5.1.4 核心方法 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)
void writeUTF (String str)
int size ()
DataInputStream (实现了 DataInput 接口):
boolean readBoolean ()
byte readByte ()
short readShort ()
char readChar ()
int readInt ()
long readLong ()
float readFloat ()
double readDouble ()
String readUTF ()
5.1.5 使用示例 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();
}
}
}
5.1.6 UTF-8 字符串格式说明 writeUTF()方法写入的字符串格式有特定结构:
前两个字节:字符串长度(以字节为单位)
后续字节:字符串的 Modified UTF-8 编码
Modified UTF-8 与标准 UTF-8 的区别:
空字符('\u0000')编码为两个字节(0xC0, 0x80)而不是一个字节
只支持 1 字节、2 字节和 3 字节的字符(基本多语言平面)
增补字符(surrogate pairs)编码为两个 3 字节序列
因此,用readUTF()读取时必须使用writeUTF()写入的格式。
第六部分:管道流
6.1 PipedInputStream 和 PipedOutputStream
6.1.1 基本概念 PipedInputStream和PipedOutputStream用于在同一 JVM 中的不同线程之间建立通信管道。一个线程通过PipedOutputStream写入数据,另一个线程通过PipedInputStream读取数据。
6.1.2 特点
线程间通信 :专为多线程环境设计
阻塞操作 :读空或写满时会阻塞线程
有限缓冲区 :默认缓冲区大小为 1024 字节
一对一连接 :一个输入流只能连接一个输出流
6.1.3 构造方法
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);
6.1.4 连接方法
pis.connect(PipedOutputStream src);
pos.connect(PipedInputStream snk);
6.1.5 使用示例 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);
} 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();
}
}
}
6.1.6 注意事项
必须在多线程环境使用 :单线程中使用可能导致死锁
避免死锁 :读写线程互相等待可能导致死锁
缓冲区满 :默认缓冲区较小,大数据传输时需调整
流关闭 :正确关闭流,避免资源泄漏
第七部分:回退输入流
7.1 PushbackInputStream
7.1.1 基本概念 PushbackInputStream是一个特殊的输入流装饰器,允许将读取的字节推回到流中,以便重新读取。这在解析器实现中特别有用,比如需要预读数据来决定下一步操作。
7.1.2 工作原理 PushbackInputStream内部维护一个回退缓冲区(字节数组)。当调用unread()方法时,数据被写入这个缓冲区。读取操作优先从回退缓冲区读取数据,只有当缓冲区为空时才从底层输入流读取。
7.1.3 构造方法
PushbackInputStream pbis = new PushbackInputStream (InputStream in);
PushbackInputStream pbis = new PushbackInputStream (InputStream in, int size);
7.1.4 核心方法
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
7.1.5 使用示例 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 ;
}
if (isDigit(b)) {
return readNumber(b);
} else if (isLetter(b)) {
return readWord(b);
} else if (isOperator(b)) {
return readOperator(b);
} else {
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();
}
}
}
7.1.6 性能考虑
回退缓冲区使用从后往前填充的方式,这样读取时可以从前往后访问
缓冲区满时会抛出IOException
频繁的 unread 操作会增加内存复制开销
第八部分:打印流
8.1 PrintStream
8.1.1 基本概念 PrintStream是一个功能丰富的输出流装饰器,提供了打印各种数据类型的便利方法。最著名的PrintStream实例就是System.out和System.err。
8.1.2 特点
便利的输出方法 :提供重载的print()和println()方法
格式化输出 :支持printf()和format()方法
不抛出 IOException :异常被内部捕获,可通过checkError()检查状态
自动刷新 :可配置为遇到换行符时自动刷新
字符编码 :支持指定字符编码
8.1.3 构造方法
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);
8.1.4 核心方法
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)
PrintStream printf (String format, Object... args)
PrintStream format (String format, Object... args)
boolean checkError ()
protected void clearError ()
8.1.5 使用示例 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 );
ps.format("圆周率:%.10f%n" , Math.PI)
.format("当前时间:%tF %<tT%n" , new Date ());
if (ps.checkError()) {
System.err.println("写入过程中发生错误" );
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class RedirectSystemOutExample {
public static void main (String[] args) {
PrintStream originalOut = System.out;
try {
PrintStream fileOut = new PrintStream (new FileOutputStream ("console.log" , true ), true , "UTF-8" );
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.setOut(originalOut);
System.out.println("已恢复控制台输出" );
}
}
private static void logMessage (String message) {
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();
}
}
}
}
8.1.6 重要注意事项
异常处理 :PrintStream 不会抛出 IOException,必须通过checkError()检查错误
性能考虑 :频繁的 print() 调用仍然会加锁,多线程环境下可以考虑缓冲
自动刷新 :启用 autoFlush 时,写入字节数组、调用 println() 或遇到'\n'时会自动刷新
字符编码 :处理非 ASCII 字符时必须指定正确的编码
第九部分:过滤流
9.1 FilterInputStream 和 FilterOutputStream
9.1.1 基本概念 FilterInputStream和FilterOutputStream是装饰器模式中的装饰者基类。它们本身不添加额外功能,只是简单地将所有方法调用转发给底层流。所有装饰器类(如 BufferedInputStream、DataInputStream)都继承自这些过滤流。
9.1.2 自定义过滤流示例
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;
}
}
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" ;
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();
}
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();
}
}
}
第十部分:序列输入流
10.1 SequenceInputStream
10.1.1 基本概念 SequenceInputStream可以将多个输入流连接成一个输入流,按顺序读取每个流,直到所有流都读完。这在处理分段文件、合并多个数据源时非常有用。
10.1.2 构造方法
SequenceInputStream sis = new SequenceInputStream (Enumeration<? extends InputStream > e);
SequenceInputStream sis = new SequenceInputStream (InputStream s1, InputStream s2);
10.1.3 使用示例 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();
}
}
}
10.1.4 注意事项
关闭SequenceInputStream会自动关闭所有包含的输入流
适合处理流式数据,不适合随机访问
可以用于分割文件的重新组装
第十一部分:校验和流
11.1 CheckedInputStream 和 CheckedOutputStream
11.1.1 基本概念 CheckedInputStream和CheckedOutputStream是 Java 标准库中用于计算数据校验和的过滤流。它们在读写数据的同时更新校验和,常用于数据完整性验证。
11.1.2 构造方法
CheckedInputStream cis = new CheckedInputStream (InputStream in, Checksum cksum);
CheckedOutputStream cos = new CheckedOutputStream (OutputStream out, Checksum cksum);
Checksum getChecksum ()
11.1.3 Checksum 实现类
CRC32 :32 位循环冗余校验
Adler32 :比 CRC32 更快但可靠性略低的校验算法
可自定义实现Checksum接口
11.1.4 使用示例 import java.util.zip.CRC32;
import java.util.zip.Adler32;
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;
public class ChecksumExample {
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 ];
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();
}
}
}
第十二部分:压缩流
12.1 GZIPInputStream 和 GZIPOutputStream
12.1.1 基本概念 Java 的java.util.zip包提供了多种压缩和解压缩流,其中最常用的是GZIPInputStream和GZIPOutputStream,它们实现了 GZIP 文件格式的压缩和解压缩。
12.1.2 构造方法
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);
12.1.3 使用示例 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);
}
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();
}
}
}
12.2 ZipInputStream 和 ZipOutputStream
12.2.1 基本概念 ZipInputStream和ZipOutputStream用于处理 ZIP 格式的压缩文件,支持包含多个条目的 ZIP 档案。
12.2.2 使用示例 import java.util.zip.*;
public class ZipExample {
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()) {
ZipEntry zipEntry = new ZipEntry (entry.getKey());
zos.putNextEntry(zipEntry);
zos.write(entry.getValue());
zos.closeEntry();
}
System.out.println("ZIP 文件创建完成:" + zipFileName);
}
}
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();
}
}
}
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));
String zipFile = "archive.zip" ;
createZipFile(zipFile, files);
System.out.println("\n使用 ZipInputStream 读取:" );
readZipFile(zipFile);
System.out.println("\n使用 ZipFile 读取:" );
readWithZipFile(zipFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
第十三部分:组合流与最佳实践
13.1 流的装饰器模式 Java I/O 库广泛使用了装饰器模式(Decorator Pattern),允许动态地为流添加功能。这种设计使得组合不同功能的流变得非常简单。
InputStream input = new BufferedInputStream (
new GZIPInputStream (
new FileInputStream ("data.gz"
)
);
13.2 常用组合模式 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();
}
}
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;
}
}
13.3 性能优化最佳实践
13.3.1 缓冲区大小选择 public class BufferSizeOptimization {
public static int getOptimalBufferSize () {
String os = System.getProperty("os.name" ).toLowerCase();
if (os.contains("linux" )) {
return 8192 ;
} else if (os.contains("win" )) {
return 65536 ;
} else {
return 16384 ;
}
}
public static InputStream createOptimizedInputStream (File file) throws IOException {
int bufferSize = getOptimalBufferSize();
return new BufferedInputStream (new FileInputStream (file), bufferSize);
}
}
13.3.2 避免常见陷阱 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();
}
}
public static void goodCloseOrder (String file) throws IOException {
try (FileOutputStream fos = new FileOutputStream (file);
GZIPOutputStream gzos = new GZIPOutputStream (fos)) {
gzos.write("data" .getBytes());
}
}
}
13.4 异常处理最佳实践 public class ExceptionHandlingBestPractices {
public static void safeFileCopy (File source, File dest) {
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);
}
}
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();
}
}
}
}
}
第十四部分:总结与选择指南
14.1 实现类对比表 实现类 类型 主要用途 特点 适用场景 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 处理流 校验和计算 数据完整性验证 文件传输验证、存储校验
14.2 选择指南
小文件:直接使用 FileInputStream/FileOutputStream
大文件:使用 BufferedInputStream/BufferedOutputStream 包装
需要频繁读写:始终使用缓冲流
临时数据:使用 ByteArrayInputStream/ByteArrayOutputStream
数据格式转换:内存流作为中间缓冲区
对象存储:使用 ObjectInputStream/ObjectOutputStream
基本类型数据:使用 DataInputStream/DataOutputStream
文本数据:使用字符流(Reader/Writer)
高效传输:组合 BufferedOutputStream 和 DataOutputStream
对象传输:组合 ObjectOutputStream 和缓冲流
压缩传输:组合 GZIPOutputStream 和缓冲流
线程间数据交换:使用 PipedInputStream/PipedOutputStream
注意:必须在不同线程中使用,避免死锁
需要预读功能:使用 PushbackInputStream
需要格式化输出:使用 PrintStream
需要数据验证:使用 CheckedInputStream/CheckedOutputStream
需要处理 ZIP 档案:使用 ZipInputStream/ZipOutputStream 或 ZipFile
14.3 性能考虑要点
缓冲区大小 :8KB-64KB 通常是最佳范围
减少系统调用 :每次read()/write()都可能触发系统调用
批量操作 :尽量使用数组参数的read(byte[])和write(byte[])方法
避免频繁创建流 :流创建有开销,考虑复用
及时关闭 :使用 try-with-resources 确保资源释放
14.4 最终建议 Java 的 I/O 体系虽然庞大,但掌握了核心概念和常用实现类后,可以灵活组合出满足各种需求的解决方案。建议:
优先使用缓冲流提高性能
始终使用 try-with-resources 自动管理资源
根据数据类型选择合适的流(字节流还是字符流)
注意字符编码问题,始终明确指定编码
理解装饰器模式,学会组合不同功能的流
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online