Java 零基础教程

Java 文件输入与输出

在之前的章节中,我们学习了如何通过 Scanner 类与控制台进行交互。然而,在现实世界的应用程序中,数据通常存储在文件中。

本章将介绍使用 Java 读取和写入文件的基本概念。掌握文件 I/O(输入/输出)对于数据处理、配置管理和应用程序数据的持久化存储相当重要。它建立在你现有的输入/输出流知识基础上,并为你学习更高级的数据处理技术做好准备。

1. 理解流 (Streams)

在 Java 中,文件的输入输出是通过流(Stream)来处理的。流代表一串连续的数据。

  • 输入流 (Input Streams):用于从源(如文件)读取数据。
  • 输出流 (Output Streams):用于向目的地(如文件)写入数据。

你可以把流想象成一条单向传输数据的“管道”。

1.1 输入流

输入流允许你从源读取数据。数据可以以多种格式读取,如字节、字符或对象。Java 中常见的输入流类包括:

  • FileInputStream: 从文件中读取原始字节。
  • FileReader: 从文件中读取字符。
  • BufferedReader: 从字符输入流中读取文本,并进行缓冲,从而实现字符、数组和行的高效读取。通常与 FileReader 配合使用。

1.2 输出流

输出流允许你将数据写入目的地。常见的输出流类包括:

  • FileOutputStream: 将原始字节写入文件。
  • FileWriter: 将字符写入文件。
  • BufferedWriter: 将文本写入字符输出流,并进行缓冲。通常与 FileWriter 配合使用。

2. 从文件中读取数据

我们重点介绍如何读取文本文件,这是开发中最常见的任务。

2.1 使用 FileReader 和 BufferedReader

FileReader 用于读取字符,但一次只读一个字符效率较低。因此,我们通常用 BufferedReader 来包装它,提供缓冲区支持。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class ReadFileExample {
    public static void main(String[] args) {
        String filePath = "data.txt"; // 请替换为你电脑上的实际文件路径
        
        // 使用 try-with-resources 自动关闭资源
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            // 逐行读取,直到文件末尾(null)
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("读取文件时出错: " + e.getMessage());
        }
    }
}

要点解析:

  • Try-with-resources: try (BufferedReader reader = ...) 这种写法能确保文件在操作完成后自动关闭,即使发生异常也不例外。这是防止资源泄漏的最佳实践。
  • readLine(): 该方法每次读取一整行文本并返回字符串。如果读到文件末尾,则返回 null

2.2 使用 Scanner 读取文件

虽然 BufferedReader 适合按行读取,但如果你需要解析特定格式的数据(如数字),使用 Scanner 会更方便。

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ReadFileScanner {
    public static void main(String[] args) {
        String filePath = "data.txt";
        try (Scanner scanner = new Scanner(new File(filePath))) {
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            System.err.println("找不到文件: " + e.getMessage());
        }
    }
}

3. 向文件中写入数据

3.1 使用 FileWriter 和 BufferedWriter

与读取类似,写入文件时也推荐使用带缓冲的包装类。

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class WriteFileExample {
    public static void main(String[] args) {
        String filePath = "output.txt";
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write("这是第一行文本。");
            writer.newLine(); // 写入一个跨平台的换行符
            writer.write("这是第二行文本。");
        } catch (IOException e) {
            System.err.println("写入文件时出错: " + e.getMessage());
        }
    }
}

3.2 追加内容到文件

默认情况下,FileWriter 会覆盖文件的原有内容。如果你想在文件末尾追加数据,需要在构造函数中传入 true

// 第二个参数 true 表示开启追加模式
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, true))) {
    writer.newLine();
    writer.write("这是追加的内容。");
}

4. 最佳实践与注意事项

  1. 务必关闭流:始终记得关闭流以释放系统资源。强烈建议使用 try-with-resources 语法。
  2. 处理异常:文件操作极易出错(文件不存在、权限不足等),必须通过 catch 捕获 IOException
  3. 选择正确的流:
    • 处理文本(.txt, .java, .csv):用 FileReader/FileWriter
    • 处理二进制(图片, 视频, .exe):用 FileInputStream/FileOutputStream
  4. 使用缓冲:无论是读还是写,包装一层 BufferedReader/BufferedWriter 都能显著提升性能。
  5. 文件路径:
    • 绝对路径:如 "C:/data/test.txt"
    • 相对路径:如 "data.txt",这是相对于程序当前运行目录的路径。