Java I/O 流复制文件练习
本练习演示了如何使用 字符缓冲流 和 字节缓冲流 实现文件的复制。其中 BufferedReader + BufferedWriter 适合复制文本文件,而 BufferedInputStream + BufferedOutputStream 可以复制任意类型的文件(图片、视频、可执行文件等)。
1. 字符缓冲流 – 文本文件复制
package blogAissgn;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class TextFileCopy {
public static void main(String[] args) {
Path sourcePath = Paths.get("C:\\Users\\lenovo\\IdeaProjects\\javaLab\\src\\blogAissgn\\sourceText.txt");
Path copyPath = Paths.get("C:\\Users\\lenovo\\IdeaProjects\\javaLab\\src\\blogAissgn\\copyText.txt");
try (BufferedReader reader = Files.newBufferedReader(sourcePath);
BufferedWriter writer = Files.newBufferedWriter(copyPath)) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine(); // 写入换行符(适配当前操作系统)
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
用法说明
| 类/方法 | 作用 |
|---|---|
Files.newBufferedReader(Path) |
打开一个文件,返回带缓冲的字符输入流(默认UTF-8编码),适合按行读取文本。 |
BufferedReader.readLine() |
读取一行文本(不包含换行符),返回 null 表示文件结束。 |
Files.newBufferedWriter(Path) |
打开一个文件,返回带缓冲的字符输出流(默认覆盖已有文件)。 |
writer.write(String) |
写入字符串。 |
writer.newLine() |
写入当前操作系统的换行符(Windows: \r\n,Linux/macOS: \n)。 |
注意:
- 只适合处理文本文件(若用于图片等二进制文件会损坏数据)。
- 使用
try-with-resources会自动关闭流并刷新缓冲区。- 默认字符集为 UTF-8,如需其他编码可通过第二个参数指定,例如:
Files.newBufferedReader(path, StandardCharsets.ISO_8859_1)。
2. 字节缓冲流 – 万能文件复制
package blogAissgn;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class AllFileCopy {
public static void main(String[] args) {
Path sourceFile = Paths.get("C:\\Users\\lenovo\\IdeaProjects\\javaLab\\src\\blogAissgn\\sourcePicture.jpg");
// 自动从源文件名提取文件名,保存到桌面同名文件
String fileName = sourceFile.getFileName().toString();
Path targetPath = Paths.get("C:\\Users\\lenovo\\Desktop\\" + fileName);
try (BufferedInputStream inputStream = new BufferedInputStream(Files.newInputStream(sourceFile));
BufferedOutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(targetPath))) {
byte[] buffer = new byte[8192]; // 8KB 缓冲区
int size;
while ((size = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, size);
}
System.out.println("复制成功:" + targetPath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
用法说明
| 类/方法 | 作用 |
|---|---|
Files.newInputStream(Path) |
打开一个文件,返回字节输入流(无缓冲)。 |
new BufferedInputStream(InputStream) |
用缓冲流包装底层输入流,减少系统调用,提高读取效率。 |
new BufferedOutputStream(OutputStream) |
用缓冲流包装底层输出流,数据先写入缓冲区,满后自动写入磁盘(也可手动 flush())。 |
inputStream.read(byte[] buffer) |
尽可能读满缓冲区,返回实际读取的字节数;返回 -1 表示文件结束。 |
outputStream.write(byte[] b, int off, int len) |
将缓冲区中的部分数据写入输出流。 |
注意:
- 字节流不关心内容编码,可以复制任何类型的文件(图片、视频、压缩包、可执行程序等)。
- 缓冲区大小通常设为 4KB、8KB 或 16KB,过小会频繁 I/O,过大浪费内存。
- 必须使用
write(buffer, 0, size)而不是write(buffer),因为最后一次读取可能填不满缓冲区。- 目标文件若已存在,默认被覆盖;若需追加数据,可使用
StandardOpenOption.APPEND。
3. 两种方式的对比
| 特性 | 字符流 (BufferedReader/Writer) | 字节流 (BufferedInputStream/OutputStream) |
|---|---|---|
| 处理单位 | 字符(char) | 字节(byte) |
| 适用场景 | 纯文本文件(.txt, .java, .csv 等) | 任意文件(图片、视频、程序、文本等) |
| 是否支持编码转换 | 是(可指定字符集) | 否(直接复制二进制数据) |
| 常用方法 | readLine(),write(String) |
read(byte[]),write(byte[], off, len) |
| 自动处理换行符 | newLine() 会自动适配系统 |
需要手动写入 \r\n 或 \n |
结论:复制文本文件两种方式都能用,但字符流更方便;复制非文本文件必须使用字节流。
4. 常见问题与注意事项
4.1 文件找不到 (NoSuchFileException)
- 检查路径是否存在,文件名是否拼写正确(区分大小写,Windows 不区分但建议一致)。
- 使用绝对路径可以避免相对路径带来的不确定性。
- 建议在代码开头打印当前工作目录:
System.out.println(System.getProperty("user.dir"));
4.2 目标路径为目录时的处理
- 输出流的目标必须是一个文件,不能是目录。
- 正确写法:
Paths.get("C:/Users/lenovo/Desktop/copy.jpg") - 错误写法:
Paths.get("C:/Users/lenovo/Desktop")(会抛出FileNotFoundException)
4.3 追加内容 vs 覆盖内容
Files.newOutputStream(path)默认覆盖原有文件。- 如需追加内容,使用
StandardOpenOption.APPEND:
Files.newOutputStream(path, StandardOpenOption.APPEND)
4.4 及时刷新缓冲区
- 使用
try-with-resources时,流会自动关闭并刷新,无需手动flush()。 - 若未使用自动关闭,请在关闭前调用
flush()确保数据写入磁盘。
4.5 大文件处理
- 使用固定大小的缓冲区循环读写,避免将整个文件加载到内存。
- 缓冲区大小推荐 8192 字节(8KB)或 16384 字节(16KB),与磁盘块大小对齐。
5. 扩展思考
-
如何使用
java.nio.file.Files.copy()一行代码完成复制?
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); -
如果要显示复制进度,如何实现?
- 先获取源文件总大小:
Files.size(sourcePath) - 在循环中累加已读取字节数,计算百分比输出。
- 先获取源文件总大小:
-
如何同时处理多个文件的复制(例如复制整个文件夹)?
- 使用
Files.walk()遍历目录树,对每个文件执行复制逻辑。
- 使用
