欢迎光临
我们一直在努力

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)
    未经允许不得转载:171主机测评 » Java 输入输出流(I/O Stream)精讲 + 文件复制实战 + 算法融合
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址