CodeGym /课程 /JAVA 25 SELF /文件操作模式:读取、写入、追加

文件操作模式:读取、写入、追加

JAVA 25 SELF
第 35 级 , 课程 3
可用

1. 覆盖写入

在处理文件时,要理解“写入文件”并非总是同一回事。有时你需要完全覆盖文件(例如保存新的报告),而有时则需要添加新信息到已有文件的末尾(例如记录应用的日志)。还有一些情况下,只需读取文件内容而不修改它。

在 Java(尤其是现代的 java.nio.file 包)中,文件的操作模式由你传入写入方法(例如 Files.write())的一组选项决定。这些选项允许你明确指定是要覆盖文件还是在末尾追加内容。

工作原理

当你调用 Files.write(path, data) 时,Java 默认会创建新文件,或者覆盖已存在的文件。文件中旧的全部内容都会被清除,只保留新数据。

这种默认行为相当于对文件进行一次“硬重置”。如果文件里之前有1000 行,而你只写入了一行——先前的所有内容都会不留痕迹地消失。

示例:将字符串写入文件

import java.nio.file.*;
import java.io.IOException;
import java.util.List;

public class OverwriteFileExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");
        List<String> lines = List.of("你好,世界!", "这是文件中的新记录。");

        // 将行写入文件(旧内容将被删除)
        Files.write(path, lines);

        System.out.println("文件已成功覆盖!");
    }
}

运行上述代码后,文件 myfile.txt 中只会保留列表 lines 的两行。之前的所有内容都会消失(Java 不会“心慈手软”)。

2. 追加(append):在文件末尾添加数据

在某些任务中(例如事件日志、记录、数据累积),需要在不破坏旧内容的情况下,将新行追加到末尾。在旧 API 中常使用带 append = true 标志的 FileWriter。在现代 API 中,这更简单且更显式。

如何实现

你只需在 Files.write() 方法中传入选项 StandardOpenOption.APPEND

import java.nio.file.*;
import java.io.IOException;
import java.util.List;

public class AppendFileExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");
        List<String> moreLines = List.of("又新增了一行。", "再来一行!");

        // 在文件末尾追加行(旧内容保留)
        Files.write(path, moreLines, StandardOpenOption.APPEND);

        System.out.println("已在文件末尾添加行!");
    }
}

重要提示:如果文件不存在,尝试追加会抛出错误——在 APPEND 模式下 Java 不会自动创建文件。若文件不存在需要创建,请同时使用两个选项:

Files.write(path, moreLines, StandardOpenOption.APPEND, StandardOpenOption.CREATE);

实践中的表现

  • 以写入(覆盖)的方式运行程序——文件中有两行。
  • 以追加的方式运行程序——在这两行之后添加新行,旧内容保留。

3. 组合选项:CREATE、APPEND、TRUNCATE_EXISTING

在 Java 中可以组合多个选项,以更灵活地控制文件打开模式:

  • StandardOpenOption.CREATE —— 如果文件不存在则创建。
  • StandardOpenOption.APPEND —— 在末尾追加数据。
  • StandardOpenOption.TRUNCATE_EXISTING —— 如果文件存在,将其截断为零字节(清空)。
  • StandardOpenOption.CREATE_NEW —— 创建新文件;如果已存在则报错。

示例:若文件不存在则创建,并在末尾追加数据

Files.write(path, moreLines, StandardOpenOption.CREATE, StandardOpenOption.APPEND);

示例:创建文件或清空并写入新数据

Files.write(path, lines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

表格:主要写入模式

选项 行为
(默认) 创建文件或覆盖已存在的文件
APPEND
在末尾追加;若文件不存在则报错
APPEND + CREATE
在末尾追加;若文件不存在则创建
TRUNCATE_EXISTING
将文件截断为零并写入新数据
CREATE_NEW
创建新文件;若文件已存在则报错

4. 二进制文件的读写

到目前为止,我们一直在处理字符串。但有时需要读写“原始”字节(例如图像、压缩包、PDF 文件)。这种情况下请使用:

  • Files.readAllBytes(path) —— 将文件读入字节数组。
  • Files.write(path, byteArray) —— 将字节数组写入文件。

示例:复制文件

import java.nio.file.*;
import java.io.IOException;

public class CopyBinaryFileExample {
    public static void main(String[] args) throws IOException {
        Path source = Paths.get("logo.png");
        Path target = Paths.get("logo_copy.png");

        byte[] data = Files.readAllBytes(source);
        Files.write(target, data);

        System.out.println("文件已复制!");
    }
}

示例:追加二进制数据

byte[] moreData = new byte[] {1, 2, 3, 4, 5};
Files.write(path, moreData, StandardOpenOption.APPEND, StandardOpenOption.CREATE);

注意:将二进制数据追加到已经包含结构化数据(例如图片)的文件中,通常会导致文件损坏。仅对文本文件或经过特殊设计的二进制文件(例如日志)使用追加。

5. 什么时候需要使用流(FileInputStreamBufferedReader 等)

Files.readAllBytes()Files.write() 对于小到中等大小的文件非常方便,可以一次性将内容读入内存。如果文件很大(例如数 GB),或者你希望按行读取(例如分析日志),请使用流。

示例:使用 BufferedReader 按行读取文件

import java.nio.file.*;
import java.io.*;

public class ReadLinesExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");

        try (BufferedReader reader = Files.newBufferedReader(path)) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("行:" + line);
            }
        }
    }
}

示例:使用 BufferedWriter 按行写入并追加

import java.nio.file.*;
import java.io.*;
import java.nio.charset.StandardCharsets;

public class WriteAppendExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");

        try (BufferedWriter writer = Files.newBufferedWriter(
                path,
                StandardCharsets.UTF_8,
                StandardOpenOption.CREATE,
                StandardOpenOption.APPEND)) {
            writer.write("通过 BufferedWriter 再写一行!");
            writer.newLine();
        }
    }
}

6. 实践:创建文件,并向其中追加行

让我们把一切串起来。假设我们有一个程序,将任务列表(todo-list)保存在文本文件中。每当用户添加新任务时,我们就把它追加到文件末尾。

import java.nio.file.*;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;

public class TodoList {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("todo.txt");
        Scanner scanner = new Scanner(System.in);

        System.out.print("请输入新任务:");
        String task = scanner.nextLine();

        // 将任务追加到文件末尾(如果不存在则创建)
        Files.write(path, 
            List.of(task), 
            StandardOpenOption.CREATE, 
            StandardOpenOption.APPEND);

        System.out.println("任务已添加!");
    }
}

多次运行该程序——每个任务都会出现在文件中的新行里。这就是我们的第一个“持久”的 todo 列表!

7. 错误处理:可能出现哪些问题?

与文件打交道总伴随出错的风险。可能发生如下情况:

  • IOException —— 所有输入/输出错误的基础异常。当文件被其他程序占用、没有读/写权限、磁盘已满等情况下可能出现。
  • NoSuchFileException —— 当你尝试读取或向不存在的文件追加(且未指定 CREATE 选项)时。
  • FileAlreadyExistsException —— 当你使用 CREATE_NEW 选项而文件已存在时。

建议:总是用 try-catch 包裹文件操作:

try {
    // 你的文件操作
} catch (IOException e) {
    System.out.println("文件操作出错:" + e.getMessage());
}

8. 使用文件打开模式时的常见错误

错误 №1:在追加时忘记指定 CREATE 选项。 如果你尝试以 APPEND 模式向一个尚不存在的文件追加,就会得到 NoSuchFileException。若希望自动创建文件,请始终添加 CREATE

错误 №2:意外覆盖文件。 不带任何其他选项调用 Files.write(path, data) 会清空文件的旧内容。若你想追加而不是清空旧内容,请使用 APPEND

错误 №3:尝试将二进制数据追加到文本文件(或反之)。 如果你把文本与二进制数据混在同一个文件中,很可能得到不可读的文件。务必为同一个文件坚持使用一种格式。

错误 №4:忘记关闭流。 如果使用流(如 BufferedWriterBufferedReader),不要忘记关闭它们(最好通过 try-with-resources),否则文件可能一直被占用。

错误 №5:未处理异常。 任何文件操作都可能抛出 IOException。如果不处理,程序会异常终止。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION