Java 输入输出流(I/O Stream)精讲 + 文件复制实战 + 算法融合
教材对应:耿祥义《Java面向对象程序设计(第4版·微课版)》第12章
核心内容:File类、字节流、字符流、缓冲流、随机流、对象流、序列化、文件锁、Scanner解析
实战项目:文本文件复制、任意文件复制、从文件读取数组并求解最大子数组和(动态规划)
课后习题:习题12全解析(含代码修正与精讲)
一、知识点全景图(PPT提炼)

该页列出了:文件字节流、文件字符流、缓冲流、随机流、数组流、数据流、对象流、序列化、文件锁、Scanner解析。
根据第12章PPT,I/O流体系可以用下表快速掌握:
| 类别 | 抽象基类 | 常用实现类(文件操作) | 单位 | 适用场景 |
|---|---|---|---|---|
| 字节流 | InputStream / OutputStream |
FileInputStream / FileOutputStream |
字节(byte) | 任意二进制文件(图片、视频、压缩包等) |
| 字符流 | Reader / Writer |
FileReader / FileWriter |
字符(char) | 纯文本文件(.txt, .java等) |
| 缓冲字节流 | - | BufferedInputStream / BufferedOutputStream |
字节 | 提高字节读写效率(带缓冲区) |
| 缓冲字符流 | - | BufferedReader / BufferedWriter |
字符 | 提高文本读写效率,支持readLine() |
| 随机流 | - | RandomAccessFile |
字节 | 任意位置读写,支持seek() |
| 数据流 | - | DataInputStream / DataOutputStream |
Java基本类型 | 以与平台无关的方式读写基本类型 |
| 对象流 | - | ObjectInputStream / ObjectOutputStream |
对象 | 对象的序列化与持久化 |
| 数组流 | - | ByteArrayInputStream / ByteArrayOutputStream |
字节/字符 | 内存中的数组作为数据源/目的地 |
| 文件锁 | - | FileLock(配合FileChannel) |
- | 多进程互斥访问文件 |
✅ 核心原则:
处理文本 优先用字符流(尤其是
BufferedReader/BufferedWriter)。复制任意文件 必须用字节流(带缓冲性能最佳)。
对象持久化 必须实现
Serializable接口。
1.2 File类常用方法(PPT 12.1节)

[截图:PPT中File类常用方法那一页]
包含:getName(),canRead(),canWrite(),exists(),length(),mkdir(),list()等。
1.3 字节流核心(PPT 12.2节:FileInputStream和FileOutputStream)


[截图:PPT中FileInputStream构造方法及read方法说明页]
[截图:PPT中FileOutputStream构造方法及write方法说明页]
关键点:
-
FileInputStream以字节为单位读取文件,read(byte[])返回实际读取字节数,-1表示末尾。 -
FileOutputStream以字节为单位写入,构造参数append=true表示追加而非覆盖。
1.4 字符流核心(PPT 12.3节:FileReader和FileWriter)

[截图:PPT中FileReader/FileWriter说明页]
1.5 缓冲流(重点 — PPT 12.4节)

[截图:PPT中BufferedReader/BufferedWriter说明页]
注意readLine()方法可以按行读取字符串。
1.6 其他流(简要)



[截图:PPT 12.6 随机流、12.8 数据流、12.10 对象流各关键页]
重点记住:
随机流
RandomAccessFile的seek()方法。对象流要求类实现
Serializable接口。对象克隆可通过对象流实现(写入再读回)。
二、实验环境准备
-
JDK版本:8 或更高
-
IDE:IntelliJ IDEA / Eclipse / VS Code
-
项目结构:新建Java项目,包名
io.demo,类名见下文 -
测试文件准备(请提前在
D:/test/目录下创建):- `original.txt`(三行文本)
- `photo.jpg`(任意小图片)
- `numbers.txt`(整数序列:`-2 1 -3 4 -1 2 1 -5 4`)
- `test.txt`(纯英文三行,用于习题6)
- `English.txt`(英语训练数据,用于习题9)

三、实验一:字符缓冲流复制文本文件

📌 3.1实验目标
使用 BufferedReader / BufferedWriter 逐行复制文本文件。
📌 3.2完整代码(TextFileCopy.java)
package io.demo;
import java.io.*;
public class TextFileCopy {
public static void main(String[] args) {
String source = "D:/test/original.txt";
String dest = "D:/test/copied.txt";
try (BufferedReader br = new BufferedReader(new FileReader(source));
BufferedWriter bw = new BufferedWriter(new FileWriter(dest))) {
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
System.out.println("✅ 文本文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.3 运行结果
运行后,控制台输出:
✅ 文本文件复制成功!

3.4 验证复制结果
-
源文件
original.txt内容(三行文本):
-
目标文件
copied.txt内容:
结论:字符缓冲流适合文本文件复制,
readLine()方法非常便捷。
四、实验二:字节缓冲流复制任意文件(万能复制)
4.1 代码实现(AnyFileCopy.java)
import java.io.*;
public class AnyFileCopy {
public static void main(String[] args) {
String source = "photo.jpg";
String dest = "photo_copy.jpg";
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
System.out.println("✅ 任意文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.2 运行结果

4.3 验证复制结果
-
源图片
photo.png属性(大小、类型):
-
目标图片
photo_copy.png属性:
结论:字节缓冲流可以复制任意类型文件(图片、视频、压缩包等),配合字节数组缓冲区效率极高。这是“万能复制”的实现基础。
五、实验三:文件读取 + 最大子数组和(动态规划)
5.1 数据文件 numbers.txt
内容(一行,空格分隔整数):
5.2 代码实现(MaxSubArrayFromFile.java)
import java.io.*;
import java.util.*;
public class MaxSubArrayFromFile {
public static void main(String[] args) {
String filePath = "numbers.txt";
int[] nums = readArrayFromFile(filePath);
if (nums == null || nums.length == 0) {
System.out.println("文件为空或读取失败");
return;
}
int maxSum = maxSubArray(nums);
System.out.println("数组:" + Arrays.toString(nums));
System.out.println("最大子数组和 = " + maxSum);
}
private static int[] readArrayFromFile(String path) {
List<Integer> list = new ArrayList<>();
try (Scanner scanner = new Scanner(new File(path))) {
while (scanner.hasNextInt()) {
list.add(scanner.nextInt());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return list.stream().mapToInt(i -> i).toArray();
}
private static int maxSubArray(int[] nums) {
int curr = nums[0];
int best = nums[0];
for (int i = 1; i < nums.length; i++) {
curr = Math.max(nums[i], curr + nums[i]);
best = Math.max(best, curr);
}
return best;
}
}
5.3 运行结果
运行后,终端输出:
5.4 算法解释(经典动态规划)
-
curr = Math.max(nums[i], curr + nums[i]):决定是重新开始子数组(取当前元素自己),还是延续之前的子数组(当前元素加上之前的和)。 -
best = Math.max(best, curr):不断更新全局最优解。 -
时间复杂度 O(n),空间复杂度 O(1),是求解最大子数组和的最优算法。
融合意义:本实验将文件I/O(读取整数数组)与经典算法结合,体现了“从文件读取数据 → 处理数据 → 输出结果”的完整流程,是实际开发中的常见模式。
六、课后习题精讲 + 动手实验(第6~9题)
说明:以下给出第12章全部9道习题的题目、答案及详细解析(不插入照片)。
第1题
题目:如果按字节读取一个文件的内容,应当使用 FileInputStream 流还是 FileReader 流?
答案:FileInputStream。
解析:FileInputStream是字节输入流,按字节读取,适合所有文件(包括二进制)。FileReader是字符输入流,会进行编码转换,只能读取文本文件,且按字符读取。
第2题
题目:FileInputStream的read方法和FileReader的read方法有何不同?
答案:
-
FileInputStream.read()返回一个字节(int类型,0~255),若到达文件末尾返回-1。 -
FileReader.read()返回一个字符(int类型,0~65535),若到达文件末尾返回-1。
解析:字节流直接处理二进制数据,字符流处理经过编码解码后的字符数据。因此,对于文本文件,使用字符流更方便;对于非文本文件,必须使用字节流。
第3题
题目:BufferedReader流能直接指向一个文件对象吗?
答案:不能。
解析:BufferedReader的构造方法需要一个Reader对象作为参数,例如BufferedReader br = new BufferedReader(new FileReader("file.txt"));。它不能直接接收文件名字符串或File对象,必须先创建底层的字符输入流(如FileReader),再包装成缓冲流。
第4题
题目:使用ObjectInputStream类和ObjectOutputStream类有哪些注意事项?
答案:
-
要写入或读取的对象必须实现
Serializable接口(序列化)。 -
读写顺序必须一致(先写什么类型,后读什么类型)。
-
建议显式声明
serialVersionUID,以保证版本兼容性。 -
关闭流时,后创建的流先关闭(或使用try-with-resources自动关闭)。
解析:对象流用于对象的持久化和网络传输。只有实现了Serializable接口的类的对象才能被序列化。如果不指定serialVersionUID,JVM会根据类结构自动生成,当类发生改变时可能抛出InvalidClassException。
第5题
题目:怎样使用输入流、输出流克隆对象?
答案:将原对象写入ObjectOutputStream(目的地可以是ByteArrayOutputStream),再通过ObjectInputStream从ByteArrayInputStream中读回,读回的对象就是原对象的深克隆。
解析:这种方法利用序列化机制,将对象状态保存到字节数组,再从字节数组重建对象,产生的新对象与原对象完全独立(修改克隆对象不影响原对象)。前提是对象及其所有成员变量都是可序列化的。
第6题:RandomAccessFile 倒置读出纯英文文本文件(编程题)
题目:使用RandomAccessFile流将一个文本文件倒置读出(即从最后一个字符往前输出到控制台)。
代码(ReverseReadFile.java):
import java.io.*;
public class ReverseReadFile {
public static void main(String[] args) {
File f = new File("E.java");
try (RandomAccessFile random = new RandomAccessFile(f, "r")) {
long pos = random.length();
while (pos > 0) {
pos--;
random.seek(pos);
int c = random.readByte() & 0xFF;
if (c >= 0x80) { // 中文字符(UTF-8双字节)
pos--;
random.seek(pos);
byte[] cc = new byte[2];
random.readFully(cc);
System.out.print(new String(cc, "UTF-8"));
} else {
System.out.print((char) c);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试文件 test.txt 内容(纯英文):
Hello Java
I/O Stream
RandomAccessFile


test.txt文件内容截图。- 终端输出倒序结果截图(例如
elifFcessAmodnaR maertS O/I avaJ olleH等形式)。
解析:
-
RandomAccessFile可以随意移动文件指针(seek())。 -
从文件末尾往前读取时,需要注意中文在UTF-8编码下占用2个或3个字节,需要特殊处理。上述代码按2字节处理常见中文(GBK/UTF-8双字节)。
-
纯英文文本可以直接按单字节读取并转换为char输出。
第7题:按行添加行号写入另一个文件(编程题)
题目:使用Java的输入流、输出流将一个文本文件的内容按行读出,每读出一行就顺序添加行号,并写入另一个文件中。
代码(AddLineNumber.java):
import java.io.*;
public class AddLineNumber {
public static void main(String[] args) {
File src = new File("E.java");
File dest = new File("temp.txt");
try (BufferedReader br = new BufferedReader(new FileReader(src));
BufferedWriter bw = new BufferedWriter(new FileWriter(dest))) {
String line;
int lineNo = 0;
while ((line = br.readLine()) != null) {
lineNo++;
bw.write(lineNo + " " + line);
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}


- 终端输出“✅ 已添加行号并写入 lined.txt”截图。
- 生成的
lined.txt文件内容截图(显示行号)。
解析:
-
使用
BufferedReader按行读取,效率高且方便。 -
每读一行,行号自增1,然后写入目标文件(行号后加一个空格,再写原行内容)。
-
注意使用
newLine()写入换行符,保证不同操作系统下的兼容性。 -
使用try-with-resources自动关闭流,避免资源泄漏。
第8题:PrintStream 以文本格式输出数据(操作题)
题目:了解打印流。PrintStream类提供了一个过滤输出流,该输出流能以文本格式显示Java的数据类型。上机实习下列程序:
代码(PrintStreamDemo.java):
import java.io.*;
public class E {
public static void main(String args[]) {
try{
File file = new File("p.txt");
FileOutputStream out = new FileOutputStream(file);
PrintStream ps = new PrintStream(out);
ps.print(12345.6789);
ps.println("how are you");
ps.println(true);
ps.close();
} catch(IOException e){
}
}
}


- 终端输出“✅ 数据已写入 p.txt”截图。
- 生成的
p.txt文件内容截图(显示12345.6789how are you和true)。
答案解析:
-
PrintStream是FilterOutputStream的子类,提供了print()、println()等方法,可以将各种类型的数据以文本形式输出到目的地(文件或控制台)。 -
运行上述程序后,会在当前目录生成
p.txt,内容为:12345.6789how are you true
-
注意:
ps.print(12345.6789)没有换行,紧接着ps.println("how are you")会换行,最后ps.println(true)会另起一行输出true。 -
PrintStream与DataOutputStream的区别:DataOutputStream以二进制格式写入,人无法直接阅读;PrintStream以文本格式写入,可用记事本打开查看。
第9题:英语单词训练小软件(完整GUI)(设计题)
题目:编写一个英语单词训练的小软件,要求:
(1) 事先编辑一个文本文件English.txt,除最后一行外,每一行都是用“#”分隔的字符串,最后一行为字符串endend。除最后一行外,每一行的第1个语言符号都是一个练习填空的英语句子,第2~5个语言符号是供选择的答案,第6个是正确的答案。
(2) 程序每次读取文本文件的一行,使用字符串解析器把语言符号分解出来。将第1个语言符号放在文本框中,其余4个语言符号作为选择框(JCheckBox)的名字,供选择答案用。根据选择的情况给出分数。做完一个题目之后,单击“确定”按钮判定该题得分。单击“下一题”按钮再读取一行。当读取到endend时关闭窗口并通知用户练习已完成。单击“重新练习”按钮时,程序重新将流指向文件English.txt。
答案(核心设计思路与代码结构):
数据文件 English.txt:
He cut cloth with scissors # a couple of # a pair of # two # a # a pair of
Englishmen like beer # Most # Most of # Most of the # The most # Most
endend

代码(WordTrainer.java):
import java.io.*;
import java.util.*;
import javax.swing.*;
public class WordTrainer {
private List<String[]> questions = new ArrayList<>();
private int currentIndex = 0;
private int score = 0;
public WordTrainer(String filename) {
loadQuestions(filename);
}
private void loadQuestions(String filename) {
try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.equals("endend")) break;
String[] parts = line.split("#");
if (parts.length >= 6) {
String[] q = new String[6];
for (int i = 0; i < 6; i++) q[i] = parts[i].trim();
questions.add(q);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void start() {
if (questions.isEmpty()) {
JOptionPane.showMessageDialog(null, "没有加载到题目!请检查 English.txt 格式。");
return;
}
currentIndex = 0;
score = 0;
nextQuestion();
}
private void nextQuestion() {
if (currentIndex >= questions.size()) {
JOptionPane.showMessageDialog(null, "🎉 练习完成!\n您的总分:" + score + " / " + questions.size());
System.exit(0);
return;
}
String[] q = questions.get(currentIndex);
String sentence = q[0];
String opt1 = q[1], opt2 = q[2], opt3 = q[3], opt4 = q[4];
String correct = q[5];
String[] options = {opt1, opt2, opt3, opt4};
int answer = JOptionPane.showOptionDialog(null,
sentence + "\n\n请选择正确答案:",
"英语单词训练 - 第 " + (currentIndex + 1) + " 题",
JOptionPane.DEFAULT_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
options[0]);
if (answer >= 0 && options[answer].equals(correct)) {
score++;
JOptionPane.showMessageDialog(null, "✅ 正确!\n当前得分:" + score);
} else {
JOptionPane.showMessageDialog(null, "❌ 错误!\n正确答案是:" + correct + "\n当前得分:" + score);
}
currentIndex++;
nextQuestion();
}
public static void main(String[] args) {
WordTrainer trainer = new WordTrainer("English.txt");
trainer.start();
}
}



- 运行后第一个题目对话框截图(显示句子和选项)。
- 选择答案后正确/错误提示框截图。
- 所有题目完成后总分对话框截图。
解析:
-
使用
Scanner或BufferedReader读取文件,以#作为分隔符解析每一行。 -
GUI设计:
JTextField显示句子,JRadioButton组显示4个选项。 -
用户选择后点击“确定”按钮,程序比较所选选项与
correctAnswer,若正确则加分。 -
点击“下一题”调用
reader.readAnProblem()加载下一道题,并更新界面。 -
读到
endend时,弹出对话框提示练习完成并关闭窗口。 -
“重新练习”按钮需要重新创建
Scanner指向文件开头,并重置分数。 -
这个项目综合运用了文件I/O、字符串解析、Swing GUI和事件处理,是第12章的一个很好的综合性练习。
七、总结对比表
| 对比维度 | 字符缓冲流 | 字节缓冲流 | 随机流 | 对象流 |
|---|---|---|---|---|
| 操作单位 | 字符 | 字节 | 字节 | 对象 |
| 支持任意文件 | 否(仅文本) | 是 | 是 | 是(需序列化) |
| 典型方法 | readLine() |
read(byte[]) |
seek() |
readObject() |
| 适用场景 | 文本复制、逐行处理 | 图片、视频、压缩包 | 断点续传、日志 | 对象持久化、深克隆 |
感谢你的阅读! 如果这篇博客帮助你掌握了Java I/O流,请点赞、收藏、关注,你的支持是我最大的动力!有任何问题或建议,欢迎在评论区留言~
实验源码下载(Java I/O流实验完整代码——缓冲流·文件复制·动态规划·课后习题):
所有实验的完整代码和测试文件已打包上传至网我的资源/ GitHub,你可以通过以下链接获取并自己运行验证。
【免费】JavaI/O流实验完整代码(缓冲流·文件复制·动态规划·课后习题)资源-CSDN下载
更多推荐
所有评论(0)