教材对应耿祥义《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题

题目FileInputStreamread方法和FileReaderread方法有何不同?
答案

  • 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类有哪些注意事项?
答案

  1. 要写入或读取的对象必须实现Serializable接口(序列化)。

  2. 读写顺序必须一致(先写什么类型,后读什么类型)。

  3. 建议显式声明serialVersionUID,以保证版本兼容性。

  4. 关闭流时,后创建的流先关闭(或使用try-with-resources自动关闭)。
    解析:对象流用于对象的持久化和网络传输。只有实现了Serializable接口的类的对象才能被序列化。如果不指定serialVersionUID,JVM会根据类结构自动生成,当类发生改变时可能抛出InvalidClassException

第5题

题目:怎样使用输入流、输出流克隆对象?
答案:将原对象写入ObjectOutputStream(目的地可以是ByteArrayOutputStream),再通过ObjectInputStreamByteArrayInputStream中读回,读回的对象就是原对象的深克隆。
解析:这种方法利用序列化机制,将对象状态保存到字节数组,再从字节数组重建对象,产生的新对象与原对象完全独立(修改克隆对象不影响原对象)。前提是对象及其所有成员变量都是可序列化的。

第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)。

答案解析

  • PrintStreamFilterOutputStream的子类,提供了print()println()等方法,可以将各种类型的数据以文本形式输出到目的地(文件或控制台)。

  • 运行上述程序后,会在当前目录生成p.txt,内容为:

    12345.6789how are you
    true
  • 注意:ps.print(12345.6789)没有换行,紧接着ps.println("how are you")会换行,最后ps.println(true)会另起一行输出true

  • PrintStreamDataOutputStream的区别: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();
    }
}

  • 运行后第一个题目对话框截图(显示句子和选项)。
  • 选择答案后正确/错误提示框截图。
  • 所有题目完成后总分对话框截图。

解析

  • 使用ScannerBufferedReader读取文件,以#作为分隔符解析每一行。

  • 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下载

更多推荐