第15章:I/O 与文件
Vol 1 的最后一章。学完它,你就能用 Java 读写文件——程序真正能和"外面"打交道了。
📌 代码说明:标注"可复制运行"的代码块包含完整的
main方法。没标注的是概念示意片段。
一个故事开头
你的奶茶店运行了几个月,积累了很多订单数据。但你发现每次关掉程序,数据就全丢了——因为所有数据都存在内存里(变量、集合)。
内存是临时存储。文件是持久存储。
java
// 内存——关了就没了
List<String> orders = new ArrayList<>();
orders.add("小明:波波奶茶×2");
// 文件——下次启动还在
// menu.txt 里写了:
// 波波奶茶 15.0
// 柠檬茶 12.0I/O(Input/Output,输入/输出)就是程序和外部世界(文件、网络、键盘)交换数据的方式。这一章只讲文件 I/O——网络 I/O 是 Vol 4 的内容。
1. 文件系统操作——File
java.io.File 类代表文件和目录路径。
java
// 可复制运行,保存为 FileDemo.java
import java.io.File;
public class FileDemo {
public static void main(String[] args) throws Exception {
// 创建一个 File 对象(只是"指向"这个路径,不保证文件存在)
File file = new File("menu.txt");
System.out.println("文件名: " + file.getName());
System.out.println("路径: " + file.getPath());
System.out.println("绝对路径: " + file.getAbsolutePath());
System.out.println("是否存在: " + file.exists());
System.out.println("是文件吗: " + file.isFile());
System.out.println("是目录吗: " + file.isDirectory());
System.out.println("大小: " + file.length() + " 字节");
// 创建新文件
if (!file.exists()) {
boolean created = file.createNewFile(); // 返回 true 如果创建成功
System.out.println("创建文件: " + created);
}
// 目录操作
File dir = new File("data/orders/2026");
System.out.println("目录是否存在: " + dir.exists());
dir.mkdirs(); // 创建多级目录——mkdirs() 带 s,不是 mkdir()
System.out.println("创建目录后: " + dir.exists());
}
}常用 File 方法
java
File f = new File("test.txt");
f.exists() // 是否存在
f.isFile() // 是文件吗
f.isDirectory() // 是目录吗
f.length() // 文件大小(字节)
f.getName() // 文件名
f.getPath() // 路径
f.getAbsolutePath() // 绝对路径
f.createNewFile() // 创建新文件
f.mkdir() // 创建目录(单级)
f.mkdirs() // 创建目录(多级)
f.delete() // 删除文件或空目录
f.listFiles() // 列出目录下的文件2. 读文件
方法1:FileReader + BufferedReader(逐行读)
java
// 先准备一个文件
// 在项目目录下创建 menu.txt,内容:
// 波波奶茶,15.0
// 柠檬茶,12.0
// 杨枝甘露,18.0
// 可复制运行,保存为 ReadFileDemo.java
import java.io.*;
public class ReadFileDemo {
public static void main(String[] args) {
// try-with-resources——自动关闭文件(第11章讲过)
try (BufferedReader reader = new BufferedReader(new FileReader("menu.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
// readLine() 读到文件末尾返回 null
System.out.println(line);
}
} catch (FileNotFoundException e) {
System.out.println("文件不存在:" + e.getMessage());
} catch (IOException e) {
System.out.println("读取出错:" + e.getMessage());
}
}
}方法2:Files.readAllLines(Java 8+,最简单)
java
// 可复制运行,保存为 ReadAllDemo.java
import java.nio.file.*;
import java.io.*;
import java.util.*;
public class ReadAllDemo {
public static void main(String[] args) throws IOException {
// 一行搞定——把整个文件读成 List<String>
List<String> lines = Files.readAllLines(Paths.get("menu.txt"));
for (String line : lines) {
System.out.println(line);
}
}
}解析一行数据
java
// 概念示意
String line = "波波奶茶,15.0";
String[] parts = line.split(","); // ["波波奶茶", "15.0"]
String name = parts[0];
double price = Double.parseDouble(parts[1]);3. 写文件
方法1:FileWriter + BufferedWriter
java
// 可复制运行,保存为 WriteFileDemo.java
import java.io.*;
record OrderItem(String drink, int quantity, double total) {}
public class WriteFileDemo {
public static void main(String[] args) {
// 准备订单数据
OrderItem[] orders = {
new OrderItem("波波奶茶", 2, 30.0),
new OrderItem("柠檬茶", 1, 12.0),
new OrderItem("杨枝甘露", 3, 54.0)
};
// 写文件
try (BufferedWriter writer = new BufferedWriter(new FileWriter("orders.txt"))) {
writer.write("=== 团子奶茶店 ===");
writer.newLine();
for (OrderItem order : orders) {
String line = order.drink() + "," + order.quantity() + "," + order.total();
writer.write(line);
writer.newLine();
}
System.out.println("订单已写入 orders.txt");
} catch (IOException e) {
System.out.println("写入出错:" + e.getMessage());
}
}
}FileWriter 的第二个参数——追加模式
java
// FileWriter("orders.txt", true) → 追加到文件末尾(不覆盖)
// FileWriter("orders.txt", false) → 覆盖文件(默认)
try (BufferedWriter writer = new BufferedWriter(new FileWriter("orders.txt", true))) {
writer.write("新订单...");
writer.newLine();
}方法2:Files.write(Java 8+)
java
import java.nio.file.*;
import java.util.*;
List<String> lines = Arrays.asList("波波奶茶,15.0", "柠檬茶,12.0");
Files.write(Paths.get("new_menu.txt"), lines);4. 字节流 vs 字符流
到目前为止你看到的是字符流——专门用来读写文本文件的。还有字节流——用来读写任何文件(图片、音频、视频)。
java
// 字节流——读图片
// 概念示意
try (FileInputStream fis = new FileInputStream("logo.png");
FileOutputStream fos = new FileOutputStream("copy.png")) {
byte[] buffer = new byte[1024]; // 一次读 1KB
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}| 种类 | 字符流 | 字节流 |
|---|---|---|
| 处理 | 文本(.txt, .csv, .json) | 任意文件(图片、音频、视频) |
| 输入 | FileReader、BufferedReader | FileInputStream |
| 输出 | FileWriter、BufferedWriter | FileOutputStream |
| 日常使用 | 读写配置、日志、数据文件 | 复制文件、处理图片等 |
5. 完整例子:订单管理系统
java
// 可复制运行,保存为 OrderManager.java
import java.io.*;
import java.nio.file.*;
import java.time.*;
import java.util.*;
record Drink(String name, double price) {
public static Drink fromLine(String line) {
String[] parts = line.split(",");
return new Drink(parts[0], Double.parseDouble(parts[1]));
}
public String toLine() {
return name + "," + price;
}
}
public class OrderManager {
private static final String MENU_FILE = "menu.txt";
private static final String ORDER_FILE = "orders.log";
// 加载菜单
public static List<Drink> loadMenu() throws IOException {
List<Drink> menu = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(MENU_FILE))) {
String line;
while ((line = reader.readLine()) != null) {
if (!line.trim().isEmpty()) {
menu.add(Drink.fromLine(line));
}
}
}
return menu;
}
// 保存菜单
public static void saveMenu(List<Drink> menu) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(MENU_FILE))) {
for (Drink drink : menu) {
writer.write(drink.toLine());
writer.newLine();
}
}
}
// 记录订单
public static void logOrder(String customer, String drink, int quantity) throws IOException {
String timestamp = LocalDateTime.now().toString();
String line = timestamp + "," + customer + "," + drink + "," + quantity;
Files.write(Paths.get(ORDER_FILE),
Collections.singletonList(line),
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
}
// 查看所有订单
public static List<String> loadOrders() throws IOException {
if (!Files.exists(Paths.get(ORDER_FILE))) {
return new ArrayList<>();
}
return Files.readAllLines(Paths.get(ORDER_FILE));
}
public static void main(String[] args) {
try {
// 创建菜单
List<Drink> menu = new ArrayList<>();
menu.add(new Drink("波波奶茶", 15.0));
menu.add(new Drink("柠檬茶", 12.0));
menu.add(new Drink("杨枝甘露", 18.0));
saveMenu(menu);
System.out.println("菜单已保存");
// 读取菜单
List<Drink> loaded = loadMenu();
System.out.println("\n今日菜单:");
for (Drink d : loaded) {
System.out.println(" " + d.name() + " — " + d.price() + "元");
}
// 记录几笔订单
logOrder("小明", "波波奶茶", 2);
logOrder("小红", "柠檬茶", 1);
logOrder("小张", "杨枝甘露", 3);
System.out.println("\n订单已记录");
// 查看所有订单
System.out.println("\n历史订单:");
for (String order : loadOrders()) {
System.out.println(" " + order);
}
} catch (IOException e) {
System.err.println("IO错误:" + e.getMessage());
}
}
}本章小结
- File——操作文件系统(存不存在、路径、创建、删除)
- 字符流——读写文本文件:
FileReader/FileWriter+BufferedReader/BufferedWriter - 字节流——读写任意文件:
FileInputStream/FileOutputStream - try-with-resources——自动关闭文件资源(第11章的复习)
- Java NIO——
Files.readAllLines()、Files.write(),比传统 IO 更简洁 - 追加模式——
new FileWriter("file", true)不覆盖原文件
✅ 验收标准
完成本章后,你应该能:
- [ ] 用 File 类操作文件和目录(创建、删除、检查)
- [ ] 用字符流读写文本文件
- [ ] 用字节流读写二进制文件
- [ ] 用 try-with-resources 自动关闭资源
- [ ] 用 Java NIO 的 Files 工具类简化操作
📌 常见卡点
- 文件流用完后忘记关闭——趁早用 try-with-resources
- 字符编码问题——默认编码和平台相关,指定 UTF-8
- 绝对路径 vs 相对路径——了解工作目录概念
- 字节流的
read()返回 int 而不是 byte
🔜 现在不需要理解
- RandomAccessFile——随机访问文件的高级场景
- FileChannel 和内存映射文件
- NIO.2 的 WatchService 文件监听
- 对象序列化 ObjectOutputStream
🧪 练习
1. 写文件:创建一个 students.txt,写入三个学生的姓名和分数(格式:小明,85),每行一个。
2. 读文件:读取 students.txt,计算平均分并输出。
3. 追加数据:在 students.txt 末尾追加一个学生的数据,然后重新读取验证。
Vol 1 完结 🎉
恭喜你学完了 Vol 1 的全部 15 章。从"Hello World"到"文件读写",你已经掌握了 Java 编程的完整基础。
接下来:
- 第二卷:数据结构与算法 — 数组、链表、栈、队列、树、图
- 学习路径 — 根据你的目标推荐后续学习顺序