学习目标
掌握Java I/O流的分类体系,理解字节流与字符流的区别
I/O流的核心概念
Java I/O流是处理输入/输出的核心机制,主要分为:
输入流 vs 输出流
- 输入流:从数据源读取数据(如文件、网络)
- 输出流:向目标写入数据(如文件、控制台)
字节流 vs 字符流
- 字节流:以字节为单位操作数据(8位)
- 字符流:以字符为单位操作数据(16位Unicode)
节点流 vs 处理流
- 节点流:直接操作数据源的流
- 处理流:对其他流进行包装,增加功能
Java I/O类层次结构
java.io
├── InputStream (字节输入流)
│ ├── FileInputStream
│ ├── ByteArrayInputStream
│ ├── FilterInputStream
│ │ ├── BufferedInputStream
│ │ ├── DataInputStream
│ │ └── ...
│ └── ...
├── OutputStream (字节输出流)
│ ├── FileOutputStream
│ ├── ByteArrayOutputStream
│ ├── FilterOutputStream
│ │ ├── BufferedOutputStream
│ │ ├── DataOutputStream
│ │ └── ...
│ └── ...
├── Reader (字符输入流)
│ ├── InputStreamReader
│ │ └── FileReader
│ ├── BufferedReader
│ ├── CharArrayReader
│ └── ...
└── Writer (字符输出流)
├── OutputStreamWriter
│ └── FileWriter
├── BufferedWriter
├── CharArrayWriter
└── ...
学习小贴士
字节流直接操作原始字节数据,适合处理二进制文件(如图片、音频)。字符流基于字节流,但处理文本时自动处理字符编码,适合处理文本文件。
学习目标
掌握InputStream/OutputStream的核心方法及其常用实现类
InputStream核心方法
方法 |
描述 |
int read() |
读取一个字节,返回0-255的int值,到达结尾返回-1 |
int read(byte[] b) |
读取数据到字节数组,返回实际读取的字节数 |
int read(byte[] b, int off, int len) |
读取指定长度的数据到字节数组的指定位置 |
long skip(long n) |
跳过并丢弃n个字节的数据 |
int available() |
返回可以读取的字节数(不保证准确) |
void close() |
关闭流并释放系统资源 |
OutputStream核心方法
方法 |
描述 |
void write(int b) |
写入一个字节(低8位) |
void write(byte[] b) |
写入整个字节数组 |
void write(byte[] b, int off, int len) |
写入字节数组的指定部分 |
void flush() |
刷新输出流,强制写出所有缓冲数据 |
void close() |
关闭流并释放系统资源 |
字节流使用示例:文件复制
使用字节流实现文件复制功能:
import java.io.*;
public class FileCopyExample {
public static void main(String[] args) {
// 使用try-with-resources确保流正确关闭
try (InputStream in = new FileInputStream("source.jpg");
OutputStream out = new FileOutputStream("copy.jpg")) {
byte[] buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
// 循环读取直到文件末尾
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
System.out.println("文件复制成功!");
} catch (IOException e) {
System.err.println("文件复制出错: " + e.getMessage());
}
}
}
性能优化提示
使用缓冲区(如BufferedInputStream)可以显著提高I/O性能。在示例中,我们使用了8KB的缓冲区,这是经过实践检验的较优大小。
学习目标
理解字符编码的重要性,掌握Reader/Writer处理文本数据
字符流的核心优势
- 自动处理字符编码(如UTF-8、GBK)
- 直接操作字符而非字节,简化文本处理
- 提供便捷的逐行读写方法
Reader核心方法
方法 |
描述 |
int read() |
读取单个字符,返回0-65535的int值,结尾返回-1 |
int read(char[] cbuf) |
读取字符到数组,返回实际读取字符数 |
int read(char[] cbuf, int off, int len) |
读取指定长度到字符数组的指定位置 |
long skip(long n) |
跳过n个字符 |
void close() |
关闭流 |
Writer核心方法
方法 |
描述 |
void write(int c) |
写入单个字符 |
void write(char[] cbuf) |
写入字符数组 |
void write(char[] cbuf, int off, int len) |
写入字符数组的指定部分 |
void write(String str) |
写入字符串 |
void write(String str, int off, int len) |
写入字符串的指定部分 |
void flush() |
刷新缓冲区 |
void close() |
关闭流 |
字符流使用示例:读写文本文件
使用BufferedReader和BufferedWriter读写文本文件:
import java.io.*;
public class TextFileExample {
public static void main(String[] args) {
String inputFile = "source.txt";
String outputFile = "output.txt";
// 读取文本文件
try (BufferedReader reader = new BufferedReader(new FileReader(inputFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
String line;
int lineNumber = 1;
// 逐行读取并处理
while ((line = reader.readLine()) != null) {
// 添加行号并写入新文件
String outputLine = lineNumber + ": " + line;
writer.write(outputLine);
writer.newLine(); // 写入换行符
lineNumber++;
}
System.out.println("文件处理完成!");
} catch (IOException e) {
System.err.println("处理文件时出错: " + e.getMessage());
}
}
}
字符编码提示
在使用字符流时,始终指定字符编码以避免跨平台问题。例如:
// 指定UTF-8编码
Reader reader = new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8);
学习目标
理解适配器模式在I/O流中的应用,掌握流转换方法
转换流的工作原理
转换流(InputStreamReader/OutputStreamWriter)充当字节流和字符流之间的桥梁:
字节流 (InputStream)
→
InputStreamReader
→
字符流 (Reader)
字符流 (Writer)
→
OutputStreamWriter
→
字节流 (OutputStream)
转换流使用示例
将字节流转换为字符流并指定字符编码:
import java.io.*;
import java.nio.charset.StandardCharsets;
public class StreamConversionExample {
public static void main(String[] args) {
File file = new File("data.txt");
try (// 创建字节输出流
FileOutputStream fos = new FileOutputStream(file);
// 转换为字符输出流并指定UTF-8编码
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
BufferedWriter writer = new BufferedWriter(osw)) {
// 使用字符流写入文本
writer.write("你好,世界!");
writer.newLine();
writer.write("Hello, World!");
} catch (IOException e) {
e.printStackTrace();
}
// 读取文件
try (FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(isr)) {
System.out.println("文件内容:");
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
设计模式应用
InputStreamReader/OutputStreamWriter是适配器模式的经典实现,它们将字节流接口适配为字符流接口,使不同接口的类可以一起工作。
学习目标
掌握资源管理、异常处理和性能优化的最佳实践
关键最佳实践
1. 使用try-with-resources
确保资源(如文件流)被正确关闭,即使在发生异常的情况下:
// Java 7+ 自动资源管理
try (InputStream in = new FileInputStream("file.txt")) {
// 使用流
} catch (IOException e) {
// 异常处理
}
2. 使用缓冲区
包装节点流以提高I/O性能:
// 使用缓冲流
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
// 高效读取
}
3. 指定字符编码
始终明确指定字符编码,避免平台依赖问题:
Reader reader = new InputStreamReader(
new FileInputStream("file.txt"), StandardCharsets.UTF_8);
字节流 vs 字符流选择指南
场景 |
推荐使用 |
原因 |
二进制文件(图片、视频、压缩包) |
字节流 |
直接操作原始字节,不涉及字符编码 |
文本文件(UTF-8、GBK等编码) |
字符流 |
自动处理字符编码,提供文本处理便捷方法 |
网络数据传输 |
字节流 |
网络协议基于字节传输 |
逐行处理文本 |
字符流(BufferedReader) |
提供readLine()方法 |
高级技巧
使用NIO(New I/O)的Files类和Paths类可以简化文件操作:
// 读取文件所有行
List lines = Files.readAllLines(Paths.get("file.txt"));
常见错误及解决方案
错误1:未关闭资源
症状: 文件句柄泄漏,最终导致"Too many open files"错误
解决方案: 始终使用try-with-resources语句确保流被关闭
错误2:字符编码问题
症状: 读取文本文件时出现乱码
解决方案: 创建Reader/Writer时明确指定字符编码
错误3:忘记调用flush()
症状: 数据未完全写入文件,缓冲区未清空
解决方案: 重要操作后调用flush(),或使用自动刷新的流
文件操作错误处理
public void safeFileCopy(File source, File dest) throws IOException {
// 检查源文件是否存在且可读
if (!source.exists() || !source.isFile() || !source.canRead()) {
throw new IOException("源文件无效或不可读: " + source.getPath());
}
// 检查目标文件目录是否存在
File parentDir = dest.getParentFile();
if (parentDir != null && !parentDir.exists()) {
if (!parentDir.mkdirs()) {
throw new IOException("无法创建目录: " + parentDir.getPath());
}
}
// 执行文件复制
try (InputStream in = new FileInputStream(source);
OutputStream out = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
}
}
调试技巧
使用Java NIO的Files类进行简单文件操作:
// 复制文件(Java 7+)
Files.copy(Paths.get("source.txt"), Paths.get("dest.txt"));
练习1:文件加密解密
使用字节流实现简单的文件加密解密功能:
- 编写加密方法:读取文件,对每个字节执行异或操作(XOR)
- 编写解密方法:对加密文件再次执行相同异或操作
- 密钥使用简单的字节值(如0x55)
public class FileEncryptor {
public static void encryptFile(File input, File output, byte key) throws IOException {
try (InputStream in = new FileInputStream(input);
OutputStream out = new FileOutputStream(output)) {
int data;
while ((data = in.read()) != -1) {
out.write(data ^ key); // 异或加密
}
}
}
// 解密使用相同方法
public static void decryptFile(File input, File output, byte key) throws IOException {
encryptFile(input, output, key); // 异或特性:两次异或恢复原数据
}
}
练习2:日志文件分析
使用字符流分析日志文件:
- 读取日志文件(每行包含时间戳、日志级别、消息)
- 统计每种日志级别(INFO, WARN, ERROR)的出现次数
- 找出包含特定关键字(如"Exception")的所有行
- 将结果写入新的分析报告文件
扩展挑战
使用对象序列化(ObjectInputStream/ObjectOutputStream)保存和加载复杂对象结构:
// 保存对象
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("data.obj"))) {
oos.writeObject(myObject);
}
// 加载对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.obj"))) {
MyClass obj = (MyClass) ois.readObject();
}