一、数据流基础概念
1.1 流(Stream)的基本原理
流对象是Java IO操作的核心抽象,其工作原理类似于水流:
- 连续数据流动:数据像水流一样连续传输
- 可控制流量:支持按需读取/写入不同大小的数据块
- 单向性:输入流负责读取,输出流负责写入
核心优势:
- 内存效率高,支持大文件处理
- 灵活的读写控制策略
- 统一的编程接口
二、字节流操作
2.1 InputStream输入流
InputStream是字节输入流的抽象基类,FileInputStream是其常用实现。
构造方法
java
FileInputStream(File file) // 通过File对象创建
FileInputStream(String path) // 通过文件路径创建
核心方法
| 方法签名 | 功能说明 |
|---|---|
int read() | 读取单个字节,返回0-255,结束返回-1 |
int read(byte[] b) | 读取字节数组长度的数据 |
int read(byte[] b, int off, int len) | 读取指定范围的数据 |
void close() | 关闭流释放资源 |
资源管理的重要性
必须显式关闭流的原因:
- 文件描述符是有限的操作系统资源
- 每个打开文件占用文件描述符表的一个表项
- 不及时释放会导致资源泄漏,影响程序稳定性
实践示例
基础读取示例:
java
// 传统try-catch-finally方式(已过时)
InputStream inputStream = null;
try {
inputStream = new FileInputStream("test.png");
// 读取操作...
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 推荐:try-with-resources(Java 7+)
try (InputStream inputStream = new FileInputStream("test.png")) {
int byteData;
while ((byteData = inputStream.read()) != -1) {
System.out.println(byteData);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
批量读取优化:
java
try (InputStream inputStream = new FileInputStream("test.png")) {
byte[] buffer = new byte[1024]; // 1KB缓冲区
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
// 处理读取到的数据
for (int i = 0; i < bytesRead; i++) {
System.out.print(buffer[i] + " ");
}
System.out.println();
}
}
Scanner读取文本文件:
java
// 适用于文本文件,不适用于二进制文件
try (InputStream inputStream = new FileInputStream("test.txt");
Scanner scanner = new Scanner(inputStream)) {
while (scanner.hasNext()) {
String content = scanner.next();
System.out.print(content + " ");
}
}
2.2 OutputStream输出流
OutputStream是字节输出流的抽象基类,FileOutputStream是常用实现。
核心方法
| 方法签名 | 功能说明 |
|---|---|
void write(int b) | 写入单个字节 |
void write(byte[] b) | 写入字节数组 |
void write(byte[] b, int off, int len) | 写入指定范围数据 |
void close() | 关闭输出流 |
void flush() | 强制刷新缓冲区 |
缓冲区机制详解
- 性能优化:减少直接IO操作次数,提升写入效率
- 数据安全:
flush()确保关键数据立即持久化 - 自动管理:缓冲区满时自动触发写入操作
实践示例
基础写入操作:
java
// 覆盖模式写入(默认)
try (OutputStream outputStream = new FileOutputStream("test.txt")) {
outputStream.write(97); // 'a'
outputStream.write(98); // 'b'
outputStream.write(98); // 'b'
// 结果:abb(覆盖原内容)
}
// 追加模式写入
try (OutputStream outputStream = new FileOutputStream("test.txt", true)) {
outputStream.write(76); // 'L'
// 在原有内容后追加
}
批量写入操作:
java
try (OutputStream outputStream = new FileOutputStream("test.txt", true)) {
byte[] data = {99, 98, 97, 96, 95}; // cba`_
outputStream.write(data);
}
自动文件创建:
java
// 文件不存在时自动创建
try (OutputStream outputStream = new FileOutputStream("new_file.txt")) {
byte[] data = {99, 98, 97, 96, 95};
outputStream.write(data);
}
使用PrintWriter增强功能:
java
try (OutputStream outputStream = new FileOutputStream("test.txt");
PrintWriter writer = new PrintWriter(outputStream)) {
writer.print(98); // 写入数字
writer.println(99); // 写入数字并换行
writer.flush(); // 确保数据写入
}
三、字符流操作
3.1 字符流与字节流的区别
| 特性 | 字节流 | 字符流 |
|---|---|---|
| 数据单位 | 字节(8bit) | 字符(16bit) |
| 编码处理 | 原始二进制 | 自动字符编码转换 |
| 适用场景 | 所有文件类型 | 文本文件 |
| 核心类 | InputStream/OutputStream | Reader/Writer |
3.2 Reader字符输入流
文件读取示例:
java
public static void main(String[] args) {
try (Reader reader = new FileReader("test.txt")) {
char[] buffer = new char[1024];
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
for (int i = 0; i < charsRead; i++) {
System.out.print(buffer[i]);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
编码注意事项:
- 确保文件编码与Reader使用编码一致
- 推荐使用UTF-8编码保证兼容性
- IDE编码设置与运行环境编码保持一致
3.3 Writer字符输出流
文件写入示例:
java
try (Writer writer = new FileWriter("test.txt")) {
char[] content = {'H', 'E', 'l', 'l', 'o'};
writer.write(content);
} catch (IOException e) {
throw new RuntimeException(e);
}
四、综合实践练习
4.1 文件搜索与删除工具
java
public class FileSearchAndDelete {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要搜索的目录:");
String rootDir = scanner.next();
File directory = new File(rootDir);
if (!directory.isDirectory()) {
System.out.println("错误:输入的路径不是目录");
return;
}
System.out.println("请输入要搜索的关键字:");
String keyword = scanner.next();
searchFiles(directory, keyword);
}
public static void searchFiles(File rootDir, String keyword) {
File[] files = rootDir.listFiles();
if (files == null) return;
for (File file : files) {
System.out.println("正在扫描: " + file.getAbsolutePath());
if (file.isFile()) {
processFile(file, keyword);
} else {
searchFiles(file, keyword); // 递归搜索子目录
}
}
}
public static void processFile(File file, String keyword) {
if (file.getName().contains(keyword)) {
System.out.println("发现匹配文件: " + file.getName());
System.out.println("是否删除?(输入 y 确认删除)");
Scanner scanner = new Scanner(System.in);
String confirmation = scanner.next();
if ("y".equalsIgnoreCase(confirmation)) {
if (file.delete()) {
System.out.println("文件删除成功");
} else {
System.out.println("文件删除失败");
}
}
}
}
}
4.2 文件复制工具
java
public class FileCopyUtility {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入源文件路径:");
String sourcePath = scanner.next();
File sourceFile = new File(sourcePath);
if (!sourceFile.isFile()) {
System.out.println("错误:源文件不存在或不是文件");
return;
}
System.out.println("请输入目标文件路径:");
String destPath = scanner.next();
File destFile = new File(destPath);
// 检查目标目录是否存在
if (!destFile.getParentFile().isDirectory()) {
System.out.println("错误:目标目录不存在");
return;
}
System.out.println("源文件: " + sourceFile.getAbsolutePath());
System.out.println("目标文件: " + destFile.getAbsolutePath());
// 执行文件复制
copyFile(sourceFile, destFile);
}
public static void copyFile(File source, File destination) {
try (InputStream input = new FileInputStream(source);
OutputStream output = new FileOutputStream(destination)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
System.out.println("文件复制完成");
} catch (FileNotFoundException e) {
System.out.println("文件未找到: " + e.getMessage());
} catch (IOException e) {
System.out.println("IO错误: " + e.getMessage());
}
}
}
4.3 高级文件内容搜索工具
java
public class AdvancedFileSearch {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要搜索的目录:");
String searchDir = scanner.next();
File directory = new File(searchDir);
if (!directory.isDirectory()) {
System.out.println("错误:输入的不是有效目录");
return;
}
System.out.println("请输入要搜索的关键字:");
String keyword = scanner.next();
searchContent(directory, keyword);
}
public static void searchContent(File directory, String keyword) {
File[] files = directory.listFiles();
if (files == null) return;
for (File file : files) {
if (file.isFile()) {
processFileContent(file, keyword);
} else {
searchContent(file, keyword); // 递归搜索子目录
}
}
}
public static void processFileContent(File file, String keyword) {
// 检查文件名
if (file.getName().contains(keyword)) {
System.out.println("文件名包含关键字: " + file.getName());
return;
}
// 检查文件内容
StringBuilder content = new StringBuilder();
try (Reader reader = new FileReader(file)) {
char[] buffer = new char[1024];
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
content.append(buffer, 0, charsRead);
}
if (content.indexOf(keyword) >= 0) {
System.out.println("文件内容包含关键字: " + file.getName());
}
} catch (FileNotFoundException e) {
System.out.println("文件无法访问: " + file.getName());
} catch (IOException e) {
System.out.println("读取错误: " + file.getName());
}
}
}
五、最佳实践总结
5.1 资源管理
- 优先使用try-with-resources:确保资源自动关闭
- 及时释放资源:避免文件描述符泄漏
- 合理设置缓冲区:平衡内存使用与IO效率
5.2 异常处理
- 精确捕获异常:区分FileNotFoundException和IOException
- 提供有意义的错误信息:便于问题定位
- 资源释放保证:finally块或try-with-resources
5.3 性能优化
- 合适的缓冲区大小:通常1KB-8KB
- 批量操作优先:减少系统调用次数
- 及时刷新缓冲区:关键数据立即持久化
5.4 编码规范
- 统一字符编码:推荐UTF-8
- 清晰的命名规范:提高代码可读性
- 适当的代码注释:说明复杂逻辑
通过掌握这些文件IO操作技术,能够高效处理各种文件读写需求,为后续学习更高级的IO操作(如NIO)奠定坚实基础。