4
0
Java 入门指南:第四章 - Java IO 与文件操作
2026-09-07
2026-09-07
文章摘要
|
第三章我们深入学习了集合框架,本章将进入 Java 的输入输出(IO)系统。IO 是程序与外部世界交互的桥梁,无论是读取文件、网络通信还是控制台交互,都离不开 IO 操作。我们将从传统的 BIO(阻塞 IO)到 NIO(新 IO)进行全面讲解。
第四章:Java IO 与文件操作
4.1 IO 流概述
4.1.1 什么是 IO 流?
IO 流(Input/Output Stream)是一系列数据的序列,可以看作是一根管道,数据在管道中流动。Java 的 IO 流分为两大体系:
字节流(处理二进制数据)
├── InputStream(输入字节流)
│ ├── FileInputStream
│ ├── ByteArrayInputStream
│ ├── BufferedInputStream
│ └── ObjectInputStream
└── OutputStream(输出字节流)
├── FileOutputStream
├── ByteArrayOutputStream
├── BufferedOutputStream
└── ObjectOutputStream
字符流(处理文本数据)
├── Reader(输入字符流)
│ ├── FileReader
│ ├── BufferedReader
│ ├── InputStreamReader
│ └── StringReader
└── Writer(输出字符流)
├── FileWriter
├── BufferedWriter
├── OutputStreamWriter
└── StringWriter
4.1.2 字节流 vs 字符流
| 特性 | 字节流 | 字符流 |
|---|---|---|
| 处理单位 | 字节(8位) | 字符(16位 Unicode) |
| 适用场景 | 二进制文件(图片、音频、视频) | 文本文件 |
| 基类 | InputStream / OutputStream | Reader / Writer |
| 编码处理 | 需要手动处理 | 自动处理编码 |
public class StreamOverview {
public static void main(String[] args) {
// 1. 字节流:适合所有类型文件
try (FileInputStream fis = new FileInputStream("data.bin");
FileOutputStream fos = new FileOutputStream("output.bin")) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 2. 字符流:专门处理文本
try (FileReader fr = new FileReader("input.txt");
FileWriter fw = new FileWriter("output.txt")) {
char[] buffer = new char[1024];
int len;
while ((len = fr.read(buffer)) != -1) {
fw.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.2 File 类:文件和目录操作
File 类用于表示文件和目录的路径名,但它并不真正操作文件内容,而是操作文件本身的元数据。
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class FileDemo {
public static void main(String[] args) throws IOException {
// 1. 创建 File 对象
File file = new File("example.txt");
File dir = new File("mydir");
File absoluteFile = new File("/Users/username/Documents/file.txt");
// 2. 创建文件
boolean created = file.createNewFile();
System.out.println("文件创建" + (created ? "成功" : "失败(可能已存在)"));
// 3. 创建目录
boolean dirCreated = dir.mkdir();
System.out.println("目录创建" + (dirCreated ? "成功" : "失败"));
// 创建多级目录
File multiDir = new File("parent/child/grandchild");
boolean multiCreated = multiDir.mkdirs();
System.out.println("多级目录创建" + (multiCreated ? "成功" : "失败"));
// 4. 文件信息
System.out.println("=== 文件信息 ===");
System.out.println("名称:" + file.getName());
System.out.println("绝对路径:" + file.getAbsolutePath());
System.out.println("路径:" + file.getPath());
System.out.println("父目录:" + file.getParent());
System.out.println("文件大小:" + file.length() + " bytes");
// 5. 判断方法
System.out.println("\n=== 判断 ===");
System.out.println("是否存在:" + file.exists());
System.out.println("是否文件:" + file.isFile());
System.out.println("是否目录:" + file.isDirectory());
System.out.println("是否隐藏:" + file.isHidden());
System.out.println("是否可读:" + file.canRead());
System.out.println("是否可写:" + file.canWrite());
// 6. 修改时间
long lastModified = file.lastModified();
if (lastModified > 0) {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date(lastModified));
System.out.println("最后修改时间:" + time);
}
// 7. 重命名
File newFile = new File("renamed.txt");
boolean renamed = file.renameTo(newFile);
System.out.println("重命名" + (renamed ? "成功" : "失败"));
// 8. 列出目录内容
File currentDir = new File(".");
String[] list = currentDir.list();
System.out.println("\n当前目录内容:");
if (list != null) {
for (String name : list) {
File item = new File(name);
String type = item.isDirectory() ? "[目录]" : "[文件]";
System.out.println(type + " " + name);
}
}
// 9. 列出所有文件(使用 FileFilter)
File[] txtFiles = currentDir.listFiles((dir2, name) -> name.endsWith(".txt"));
System.out.println("\n所有 .txt 文件:");
if (txtFiles != null) {
for (File f : txtFiles) {
System.out.println(f.getName());
}
}
// 10. 删除文件
// boolean deleted = newFile.delete();
// System.out.println("删除" + (deleted ? "成功" : "失败"));
// 11. 删除目录(必须为空)
// boolean dirDeleted = dir.delete();
// System.out.println("删除目录" + (dirDeleted ? "成功" : "失败"));
// 12. 临时文件
File tempFile = File.createTempFile("temp", ".tmp");
tempFile.deleteOnExit(); // JVM 退出时删除
System.out.println("临时文件:" + tempFile.getAbsolutePath());
// 13. 分隔符(跨平台)
System.out.println("路径分隔符:" + File.pathSeparator);
System.out.println("文件分隔符:" + File.separator);
}
}
4.3 字节流:InputStream 和 OutputStream
4.3.1 FileInputStream 和 FileOutputStream
public class ByteStreamDemo {
public static void main(String[] args) {
// 1. 写入文件
writeFile();
// 2. 读取文件
readFile();
// 3. 文件复制
copyFile("source.txt", "destination.txt");
// 4. 追加内容
appendFile("log.txt", "新的日志内容\n");
}
// 写入文件
public static void writeFile() {
// try-with-resources 自动关闭流
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
String content = "Hello, Java IO!";
byte[] bytes = content.getBytes();
fos.write(bytes);
System.out.println("文件写入成功");
} catch (IOException e) {
e.printStackTrace();
}
}
// 读取文件
public static void readFile() {
try (FileInputStream fis = new FileInputStream("output.txt")) {
// 方式1:逐个字节读取(慢)
// int b;
// while ((b = fis.read()) != -1) {
// System.out.print((char) b);
// }
// 方式2:批量读取(推荐)
byte[] buffer = new byte[1024];
int len;
StringBuilder content = new StringBuilder();
while ((len = fis.read(buffer)) != -1) {
content.append(new String(buffer, 0, len));
}
System.out.println("文件内容:" + content.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
// 文件复制
public static void copyFile(String source, String dest) {
long startTime = System.currentTimeMillis();
try (FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192]; // 8KB 缓冲区
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
long endTime = System.currentTimeMillis();
System.out.println("文件复制完成,耗时:" + (endTime - startTime) + "ms");
} catch (FileNotFoundException e) {
System.err.println("文件不存在:" + e.getMessage());
} catch (IOException e) {
System.err.println("复制失败:" + e.getMessage());
}
}
// 追加内容
public static void appendFile(String filename, String content) {
// 第二个参数 true 表示追加
try (FileOutputStream fos = new FileOutputStream(filename, true)) {
fos.write(content.getBytes());
System.out.println("追加成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.3.2 BufferedInputStream 和 BufferedOutputStream
public class BufferedStreamDemo {
public static void main(String[] args) {
// 使用缓冲流提高性能(内部缓冲区默认 8192 bytes)
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("large_file.dat"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copied_file.dat"))) {
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
// 强制刷新缓冲区(close 时会自动调用)
bos.flush();
System.out.println("缓冲复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.3.3 DataInputStream 和 DataOutputStream
用于读写 Java 基本数据类型,保持数据格式一致。
public class DataStreamDemo {
public static void main(String[] args) {
// 写入基本数据类型
try (DataOutputStream dos = new DataOutputStream(
new FileOutputStream("data.dat"))) {
dos.writeInt(100);
dos.writeDouble(99.99);
dos.writeBoolean(true);
dos.writeUTF("Hello, 世界!"); // UTF-8 编码字符串
System.out.println("数据写入完成");
} catch (IOException e) {
e.printStackTrace();
}
// 读取基本数据类型(顺序必须与写入一致)
try (DataInputStream dis = new DataInputStream(
new FileInputStream("data.dat"))) {
int i = dis.readInt();
double d = dis.readDouble();
boolean b = dis.readBoolean();
String s = dis.readUTF();
System.out.println("读取的数据:");
System.out.println("int: " + i);
System.out.println("double: " + d);
System.out.println("boolean: " + b);
System.out.println("String: " + s);
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.4 字符流:Reader 和 Writer
4.4.1 FileReader 和 FileWriter
public class CharStreamDemo {
public static void main(String[] args) {
// 1. 写入文本文件
writeText();
// 2. 读取文本文件
readText();
// 3. 带编码的读写
readWithEncoding();
}
public static void writeText() {
try (FileWriter fw = new FileWriter("chars.txt")) {
fw.write("第一行文字\n");
fw.write("第二行文字\n");
fw.write("第三行文字\n");
// 写入字符数组
char[] chars = {'J', 'a', 'v', 'a'};
fw.write(chars);
System.out.println("字符写入完成");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void readText() {
try (FileReader fr = new FileReader("chars.txt")) {
// 方式1:逐个字符读取
// int ch;
// while ((ch = fr.read()) != -1) {
// System.out.print((char) ch);
// }
// 方式2:批量读取
char[] buffer = new char[1024];
int len;
StringBuilder content = new StringBuilder();
while ((len = fr.read(buffer)) != -1) {
content.append(buffer, 0, len);
}
System.out.println("文件内容:\n" + content);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void readWithEncoding() {
// 指定编码读取(GBK)
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("gbk_file.txt"), "GBK");
BufferedReader br = new BufferedReader(isr)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.4.2 BufferedReader 和 BufferedWriter
public class BufferedCharDemo {
public static void main(String[] args) {
// 使用缓冲字符流提高文本读写效率
// 并支持按行读写
// 1. 按行写入
try (BufferedWriter bw = new BufferedWriter(
new FileWriter("lines.txt"))) {
bw.write("第一行");
bw.newLine(); // 跨平台换行
bw.write("第二行");
bw.newLine();
bw.write("第三行");
bw.flush();
System.out.println("行写入完成");
} catch (IOException e) {
e.printStackTrace();
}
// 2. 按行读取(推荐方式)
try (BufferedReader br = new BufferedReader(
new FileReader("lines.txt"))) {
String line;
int lineNum = 0;
while ((line = br.readLine()) != null) {
lineNum++;
System.out.println("第 " + lineNum + " 行: " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.4.3 PrintWriter 和 PrintStream
public class PrintDemo {
public static void main(String[] args) {
// 1. PrintWriter:便捷的文本输出
try (PrintWriter pw = new PrintWriter(new FileWriter("print.txt"))) {
pw.println("格式化输出");
pw.printf("姓名: %s, 年龄: %d, 薪资: %.2f%n", "张三", 25, 8000.50);
pw.print("自动 flush: ");
pw.flush();
System.out.println("PrintWriter 写入完成");
} catch (IOException e) {
e.printStackTrace();
}
// 2. System.out 就是 PrintStream
System.out.println("这是 System.out");
// 3. 重定向输出到文件
try (PrintStream ps = new PrintStream(new FileOutputStream("redirect.log"))) {
// 保存原始输出流
PrintStream originalOut = System.out;
// 重定向
System.setOut(ps);
System.out.println("这一行会写入到文件");
System.out.println("而不是控制台");
// 恢复
System.setOut(originalOut);
System.out.println("恢复控制台输出");
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.5 对象序列化
对象序列化(Serialization)是将 Java 对象转换为字节流,以便存储到文件或通过网络传输。
4.5.1 序列化基础
import java.io.*;
// 必须实现 Serializable 接口
class Person implements Serializable {
private static final long serialVersionUID = 1L; // 版本号
private String name;
private int age;
private transient String password; // transient 字段不会被序列化
private static String company = "ABC Corp"; // 静态变量不会被序列化
public Person(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
@Override
public String toString() {
return String.format("Person{name='%s', age=%d, password='%s', company='%s'}",
name, age, password, company);
}
}
public class SerializationDemo {
public static void main(String[] args) {
// 1. 序列化
serializeObject();
// 2. 反序列化
deserializeObject();
// 3. 序列化集合
serializeCollection();
}
public static void serializeObject() {
Person person = new Person("李四", 30, "secret123");
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.ser"))) {
oos.writeObject(person);
System.out.println("对象序列化成功:" + person);
System.out.println("password 字段被 transient 修饰,不会被序列化");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deserializeObject() {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.ser"))) {
Person person = (Person) ois.readObject();
System.out.println("对象反序列化成功:" + person);
System.out.println("password 为 null(因为 transient)");
System.out.println("company 为静态变量初始值(不会被序列化)");
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void serializeCollection() {
// 序列化集合(集合中的元素也必须可序列化)
Person p1 = new Person("张三", 25, "pwd1");
Person p2 = new Person("王五", 28, "pwd2");
java.util.ArrayList<Person> list = new java.util.ArrayList<>();
list.add(p1);
list.add(p2);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("list.ser"))) {
oos.writeObject(list);
System.out.println("集合序列化成功");
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化集合
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("list.ser"))) {
@SuppressWarnings("unchecked")
java.util.ArrayList<Person> deserializedList =
(java.util.ArrayList<Person>) ois.readObject();
System.out.println("集合反序列化成功:");
deserializedList.forEach(System.out::println);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
4.5.2 自定义序列化
class CustomSerializable implements Serializable {
private static final long serialVersionUID = 1L;
private String data;
private int number;
public CustomSerializable(String data, int number) {
this.data = data;
this.number = number;
}
// 自定义序列化
private void writeObject(ObjectOutputStream out) throws IOException {
// 先写入默认序列化内容
out.defaultWriteObject();
// 额外写入自定义数据
out.writeInt(number * 2); // 加密或转换
System.out.println("自定义序列化完成");
}
// 自定义反序列化
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// 先读取默认序列化内容
in.defaultReadObject();
// 读取自定义数据
int customValue = in.readInt();
this.number = customValue / 2; // 还原
System.out.println("自定义反序列化完成");
}
@Override
public String toString() {
return String.format("Custom{data='%s', number=%d}", data, number);
}
}
public class CustomSerializationDemo {
public static void main(String[] args) throws Exception {
CustomSerializable obj = new CustomSerializable("测试数据", 100);
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("custom.ser"))) {
oos.writeObject(obj);
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("custom.ser"))) {
CustomSerializable result = (CustomSerializable) ois.readObject();
System.out.println("反序列化结果:" + result);
}
}
}
4.6 NIO(New IO)简介
Java NIO(New Input/Output)是 JDK 1.4 引入的新 IO API,提供非阻塞 IO 和更高效的文件操作。
4.6.1 核心概念
- Channel(通道):双向数据传输的通道
- Buffer(缓冲区):存储数据的容器
- Selector(选择器):多路复用器,用于非阻塞 IO
4.6.2 文件操作(NIO.2,JDK 7+)
import java.nio.file.*;
import java.nio.*;
import java.io.*;
public class NIODemo {
public static void main(String[] args) throws IOException {
// 1. 创建文件
Path path = Paths.get("nio_example.txt");
if (Files.notExists(path)) {
Files.createFile(path);
System.out.println("文件创建成功:" + path);
}
// 2. 写入文件
String content = "NIO 文件写入测试\n第二行\n第三行";
Files.write(path, content.getBytes(), StandardOpenOption.WRITE);
System.out.println("写入成功");
// 3. 读取文件
byte[] bytes = Files.readAllBytes(path);
String readContent = new String(bytes);
System.out.println("读取内容:\n" + readContent);
// 4. 按行读取
System.out.println("\n按行读取:");
Files.lines(path).forEach(System.out::println);
// 5. 复制文件
Path dest = Paths.get("nio_copy.txt");
Files.copy(path, dest, StandardCopyOption.REPLACE_EXISTING);
System.out.println("复制成功");
// 6. 移动/重命名
Path moved = Paths.get("nio_moved.txt");
Files.move(dest, moved, StandardCopyOption.REPLACE_EXISTING);
System.out.println("移动成功");
// 7. 文件属性
System.out.println("\n文件属性:");
System.out.println("大小:" + Files.size(path));
System.out.println("是否文件:" + Files.isRegularFile(path));
System.out.println("是否目录:" + Files.isDirectory(path));
System.out.println("最后修改时间:" + Files.getLastModifiedTime(path));
// 8. 遍历目录
System.out.println("\n当前目录内容:");
try (var stream = Files.list(Paths.get("."))) {
stream.forEach(p -> System.out.println(" " + p.getFileName()));
}
// 9. 递归遍历
System.out.println("\n递归遍历当前目录:");
try (var walk = Files.walk(Paths.get("."), 2)) { // 深度限制
walk.forEach(p -> {
String indent = " ".repeat(p.getNameCount() - 1);
System.out.println(indent + p.getFileName());
});
}
// 10. 删除文件
// Files.deleteIfExists(moved);
// System.out.println("文件已删除");
// 11. 创建临时文件
Path tempFile = Files.createTempFile("prefix", ".tmp");
System.out.println("临时文件:" + tempFile);
// 12. 文件监视(WatchService)
watchDirectory();
}
public static void watchDirectory() throws IOException {
// 监控目录变化
Path dir = Paths.get(".");
WatchService watchService = FileSystems.getDefault().newWatchService();
dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("\n开始监控目录变化...");
// 在实际使用中,可以在单独线程中监控
// 这里仅演示注册
watchService.close();
}
}
4.6.3 Buffer 和 Channel 操作
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class BufferChannelDemo {
public static void main(String[] args) throws IOException {
// 1. 使用 Channel 和 Buffer 读写文件
Path path = Paths.get("channel_demo.txt");
// 写入
try (FileChannel channel = FileChannel.open(path,
StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
String data = "Hello, NIO Channel!";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(data.getBytes());
buffer.flip(); // 切换为读模式
while (buffer.hasRemaining()) {
channel.write(buffer);
}
System.out.println("Channel 写入完成");
}
// 读取
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer);
buffer.flip();
byte[] bytes = new byte[bytesRead];
buffer.get(bytes);
System.out.println("Channel 读取内容:" + new String(bytes));
}
// 2. 使用 MappedByteBuffer(内存映射文件,高效)
try (FileChannel channel = FileChannel.open(path,
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
long size = channel.size();
MappedByteBuffer mappedBuffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, size);
// 直接操作缓冲区,修改会反映到文件
mappedBuffer.put(0, (byte) 'X');
System.out.println("内存映射文件修改完成");
}
}
}
4.7 实用案例
4.7.1 配置文件读取器
import java.util.*;
public class ConfigReader {
private Properties properties = new Properties();
public void load(String filePath) throws IOException {
try (InputStream input = new FileInputStream(filePath)) {
properties.load(input);
System.out.println("配置文件加载成功:" + filePath);
}
}
public String get(String key) {
return properties.getProperty(key);
}
public String get(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
public int getInt(String key, int defaultValue) {
String value = get(key);
try {
return value != null ? Integer.parseInt(value) : defaultValue;
} catch (NumberFormatException e) {
return defaultValue;
}
}
public static void main(String[] args) {
// 创建配置文件 config.properties
// database.url=jdbc:mysql://localhost:3306/test
// database.username=root
// database.password=123456
// app.name=MyApp
// app.version=1.0
ConfigReader config = new ConfigReader();
try {
config.load("config.properties");
System.out.println("数据库 URL:" + config.get("database.url"));
System.out.println("用户名:" + config.get("database.username", "admin"));
System.out.println("应用名称:" + config.get("app.name"));
System.out.println("版本:" + config.getInt("app.version", 1));
} catch (IOException e) {
System.err.println("加载配置文件失败:" + e.getMessage());
}
}
}
4.7.2 日志记录器
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
class Logger {
private static final String LOG_FILE = "application.log";
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 使用 volatile 保证可见性
private static volatile Logger instance;
private Logger() {}
public static Logger getInstance() {
if (instance == null) {
synchronized (Logger.class) {
if (instance == null) {
instance = new Logger();
}
}
}
return instance;
}
private void writeLog(String level, String message) {
String logEntry = String.format("[%s] [%s] %s%n",
LocalDateTime.now().format(FORMATTER), level, message);
// 写入日志文件
try (FileWriter fw = new FileWriter(LOG_FILE, true);
BufferedWriter bw = new BufferedWriter(fw)) {
bw.write(logEntry);
} catch (IOException e) {
System.err.println("日志写入失败:" + e.getMessage());
}
// 同时输出到控制台
System.out.print(logEntry);
}
public void info(String message) {
writeLog("INFO", message);
}
public void warn(String message) {
writeLog("WARN", message);
}
public void error(String message) {
writeLog("ERROR", message);
}
public void error(String message, Throwable e) {
writeLog("ERROR", message + " - " + e.getMessage());
// 可写入堆栈跟踪
}
}
public class LoggerDemo {
public static void main(String[] args) {
Logger logger = Logger.getInstance();
logger.info("应用程序启动");
logger.warn("内存使用率较高");
try {
int result = 10 / 0;
} catch (Exception e) {
logger.error("计算错误", e);
}
logger.info("应用程序关闭");
}
}
4.7.3 CSV 文件处理
import java.io.*;
import java.util.*;
public class CSVProcessor {
// 读取 CSV
public static List<Map<String, String>> readCSV(String filePath, String delimiter)
throws IOException {
List<Map<String, String>> rows = new ArrayList<>();
List<String> headers = null;
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
int lineNum = 0;
while ((line = br.readLine()) != null) {
lineNum++;
String[] fields = line.split(delimiter);
if (lineNum == 1) {
// 第一行是表头
headers = Arrays.asList(fields);
} else {
// 数据行
Map<String, String> row = new LinkedHashMap<>();
for (int i = 0; i < fields.length && i < headers.size(); i++) {
row.put(headers.get(i), fields[i]);
}
rows.add(row);
}
}
}
return rows;
}
// 写入 CSV
public static void writeCSV(String filePath, String delimiter,
List<String> headers, List<Map<String, String>> rows) throws IOException {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(filePath))) {
// 写入表头
bw.write(String.join(delimiter, headers));
bw.newLine();
// 写入数据
for (Map<String, String> row : rows) {
List<String> values = new ArrayList<>();
for (String header : headers) {
values.add(row.getOrDefault(header, ""));
}
bw.write(String.join(delimiter, values));
bw.newLine();
}
}
}
public static void main(String[] args) throws IOException {
String csvFile = "employees.csv";
String delimiter = ",";
// 创建示例数据
List<String> headers = Arrays.asList("id", "name", "department", "salary");
List<Map<String, String>> data = new ArrayList<>();
Map<String, String> row1 = new LinkedHashMap<>();
row1.put("id", "1");
row1.put("name", "张三");
row1.put("department", "研发部");
row1.put("salary", "8000");
data.add(row1);
Map<String, String> row2 = new LinkedHashMap<>();
row2.put("id", "2");
row2.put("name", "李四");
row2.put("department", "市场部");
row2.put("salary", "6000");
data.add(row2);
// 写入 CSV
writeCSV(csvFile, delimiter, headers, data);
System.out.println("CSV 写入成功:" + csvFile);
// 读取 CSV
List<Map<String, String>> readData = readCSV(csvFile, delimiter);
System.out.println("\nCSV 读取结果:");
for (Map<String, String> row : readData) {
System.out.println(row);
}
}
}
4.8 异常处理与最佳实践
public class IOExceptionBestPractices {
public static void main(String[] args) {
// 1. 使用 try-with-resources(自动关闭资源)
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
String line = br.readLine();
// 处理内容
} catch (IOException e) {
// 处理异常
System.err.println("读取文件失败:" + e.getMessage());
}
// 不需要 finally 块手动关闭
// 2. 分级异常处理
try {
readFile("important.txt");
} catch (FileNotFoundException e) {
System.err.println("文件未找到,请检查文件路径");
} catch (IOException e) {
System.err.println("读取文件发生IO错误");
} catch (Exception e) {
System.err.println("发生未知错误");
}
// 3. 使用具体异常类型
// 不要捕获 Exception,尽量捕获具体异常
// 4. 记录日志
try {
riskyOperation();
} catch (IOException e) {
// 记录日志(使用 Logger 或 System.err)
System.err.println("操作失败:" + e);
// 可以重新抛出或返回错误状态
}
// 5. 处理资源释放
// 使用 try-with-resources 是最好的方式
// 6. 避免空的 catch 块
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
// 不要空捕获,至少要记录日志
System.err.println("计算错误:" + e.getMessage());
}
// 7. 文件路径使用 Paths.get() 而非字符串拼接
Path path = Paths.get(System.getProperty("user.home"), "Documents", "file.txt");
System.out.println("安全路径:" + path);
}
private static void readFile(String path) throws IOException {
try (FileInputStream fis = new FileInputStream(path)) {
// 读取文件
}
}
private static void riskyOperation() throws IOException {
throw new IOException("模拟IO错误");
}
}
第五章:IO 总结与选择指南
5.1 IO 选择决策树
需要处理什么类型的数据?
│
├── 二进制数据(图片、音频、视频、序列化对象)
│ └── 使用字节流(InputStream / OutputStream)
│ ├── 简单读写 → FileInputStream / FileOutputStream
│ ├── 性能优化 → BufferedInputStream / BufferedOutputStream
│ ├── 基本数据类型 → DataInputStream / DataOutputStream
│ └── 对象序列化 → ObjectInputStream / ObjectOutputStream
│
└── 文本数据
└── 使用字符流(Reader / Writer)
├── 简单读写 → FileReader / FileWriter
├── 性能优化 + 按行读写 → BufferedReader / BufferedWriter
├── 格式化输出 → PrintWriter
├── 指定编码 → InputStreamReader / OutputStreamWriter
└── 内存操作 → StringReader / StringWriter
5.2 性能优化建议
| 场景 | 建议 |
|---|---|
| 大文件读取 | 使用缓冲流(BufferedXXX),缓冲区大小 8192 或更大 |
| 小文件读写 | 使用 NIO Files.readAllBytes() / write() |
| 频繁读写 | 使用 BufferedStream 减少系统调用 |
| 文本处理 | 使用 BufferedReader 按行读取 |
| 网络传输 | 使用 NIO 非阻塞 IO |
| 内存映射文件 | 使用 MappedByteBuffer(大文件高效) |
5.3 常见陷阱与注意
- 忘记关闭流 → 使用 try-with-resources
- 编码问题 → 始终指定字符编码
- 文件路径 → 使用
File.separator或Paths.get() - 性能问题 → 使用缓冲流
- 跨平台 → 使用
System.lineSeparator()而不是\n - 序列化版本 → 定义
serialVersionUID
结语
本章详细讲解了 Java IO 体系的方方面面,从基础的 File 类到字节流、字符流,再到高级的对象序列化和 NIO。IO 操作是每一个 Java 开发者必须掌握的技能,在实际项目中,无论是日志记录、配置文件读取、数据持久化还是网络通信,都离不开 IO。
本章要点回顾:
- ✅ File 类的文件和目录操作
- ✅ 字节流(InputStream / OutputStream)
- ✅ 字符流(Reader / Writer)
- ✅ 缓冲流提高性能
- ✅ 对象序列化和反序列化
- ✅ NIO 和 NIO.2 新特性
- ✅ 实用案例和最佳实践
建议练习:
- 实现一个文件搜索工具(递归查找指定类型文件)
- 实现一个简单的文本编辑器(打开、编辑、保存)
- 实现一个文件批量重命名工具
- 使用序列化实现对象持久化存储
下一章预告:我们将进入 Java 多线程与并发编程,学习如何让程序同时执行多个任务,以及如何安全地处理共享资源。敬请期待!🚀
