目录
- [🚀 25 IO流高级操作------序列化、NIO与Files工具类](#🚀 25 IO流高级操作——序列化、NIO与Files工具类)
-
- 一、序列化与反序列化
-
- [1.1 什么是序列化](#1.1 什么是序列化)
- [1.2 Serializable接口](#1.2 Serializable接口)
- [1.3 序列化操作](#1.3 序列化操作)
- [1.4 serialVersionUID的作用](#1.4 serialVersionUID的作用)
- [1.5 序列化集合](#1.5 序列化集合)
- 二、NIO基础
-
- [2.1 NIO概述](#2.1 NIO概述)
- [2.2 Buffer(缓冲区)](#2.2 Buffer(缓冲区))
- [2.3 Channel(通道)](#2.3 Channel(通道))
- 三、Files工具类
-
- [3.1 Files类简介](#3.1 Files类简介)
- [3.2 文件读写](#3.2 文件读写)
- [3.3 文件操作](#3.3 文件操作)
- 四、Path与Paths
-
- [4.1 Path接口](#4.1 Path接口)
- 五、try-with-resources
-
- [5.1 传统方式的问题](#5.1 传统方式的问题)
- [5.2 try-with-resources语法](#5.2 try-with-resources语法)
- [5.3 try-with-resources与异常处理](#5.3 try-with-resources与异常处理)
- 六、实战:文件操作工具类
- 七、常见面试题解析
- 八、总结与下篇预告
-
- 本篇要点
- [📢 下篇预告](#📢 下篇预告)
- [💬 互动话题](#💬 互动话题)
🚀 25 IO流高级操作------序列化、NIO与Files工具类
更新日期 :2026年5月 | Java入门到精通系列 · 第四阶段·高级特性
© 版权声明:本文为原创技术文章,转载请联系作者并注明出处。
一、序列化与反序列化
1.1 什么是序列化
序列化 :将Java对象转换为字节序列(可以存储或传输)
反序列化:将字节序列恢复为Java对象
序列化:Object → 字节序列 → 文件/网络
反序列化:文件/网络 → 字节序列 → Object
1.2 Serializable接口
java
import java.io.Serializable;
/**
* 可序列化的用户类
*/
public class User implements Serializable {
// 序列化版本号 - 强烈建议显式声明
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String email;
// transient关键字:标记不参与序列化的字段
private transient String password;
public User(String name, int age, String email, String password) {
this.name = name;
this.age = age;
this.email = email;
this.password = password;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age +
", email='" + email + "', password='" + password + "'}";
}
}
1.3 序列化操作
java
import java.io.*;
public class SerializationDemo {
// 序列化:对象 → 文件
public static void serialize(Object obj, String filePath) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeObject(obj);
oos.close();
System.out.println("✅ 序列化成功:" + filePath);
}
// 反序列化:文件 → 对象
@SuppressWarnings("unchecked")
public static <T> T deserialize(String filePath) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
T obj = (T) ois.readObject();
ois.close();
System.out.println("✅ 反序列化成功");
return obj;
}
public static void main(String[] args) throws Exception {
// 创建用户
User user = new User("张三", 25, "zhangsan@example.com", "123456");
System.out.println("原始对象:" + user);
// 序列化
serialize(user, "D:/test/user.dat");
// 反序列化
User restored = deserialize("D:/test/user.dat");
System.out.println("恢复对象:" + restored);
// 输出:User{name='张三', age=25, email='zhangsan@example.com', password='null'}
// password被transient修饰,序列化后为null
}
}
1.4 serialVersionUID的作用
java
/**
* serialVersionUID版本控制示例
*/
public class VersionDemo {
/*
* 场景1:初始版本
* private static final long serialVersionUID = 1L;
* private String name;
*/
/*
* 场景2:修改了类结构(新增字段)
* 如果不指定serialVersionUID,JVM会根据类结构自动生成
* 导致反序列化时版本号不匹配,抛出InvalidClassException
*
* 指定serialVersionUID后,即使类结构变化,只要版本号一致
* 新增字段会使用默认值(null/0/false)
*/
private static final long serialVersionUID = 1L;
private String name;
private int age; // 新增字段
}
| serialVersionUID情况 | 反序列化结果 |
|---|---|
| 未指定,类未修改 | 成功(自动生成匹配的UID) |
| 未指定,类已修改 | ❌ InvalidClassException |
| 已指定,类未修改 | 成功 |
| 已指定,类已修改 | 成功(新字段取默认值) |
1.5 序列化集合
java
import java.io.*;
import java.util.*;
public class CollectionSerialization {
// 序列化List
public static void serializeList(String filePath) throws IOException {
List<User> users = Arrays.asList(
new User("张三", 25, "zs@test.com", "123"),
new User("李四", 30, "ls@test.com", "456"),
new User("王五", 28, "ww@test.com", "789")
);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeObject(users);
oos.close();
}
// 反序列化List
@SuppressWarnings("unchecked")
public static List<User> deserializeList(String filePath) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
List<User> users = (List<User>) ois.readObject();
ois.close();
return users;
}
public static void main(String[] args) throws Exception {
serializeList("D:/test/users.dat");
List<User> users = deserializeList("D:/test/users.dat");
users.forEach(System.out::println);
}
}
二、NIO基础
2.1 NIO概述
NIO(New IO / Non-blocking IO)是Java 1.4引入的新IO API,提供了更高效的IO操作方式。
| 特性 | 传统IO | NIO |
|---|---|---|
| 数据处理 | 面向流(Stream) | 面向缓冲区(Buffer) |
| 阻塞性 | 阻塞IO | 非阻塞IO |
| 多路复用 | 不支持 | 支持(Selector) |
| 适用场景 | 连接数少、数据量大 | 连接数多、数据量小 |
2.2 Buffer(缓冲区)
java
import java.nio.ByteBuffer;
public class BufferDemo {
public static void main(String[] args) {
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配1024字节
System.out.println("===== 初始状态 =====");
System.out.println("capacity: " + buffer.capacity()); // 1024
System.out.println("position: " + buffer.position()); // 0
System.out.println("limit: " + buffer.limit()); // 1024
System.out.println("remaining: " + buffer.remaining()); // 1024
// 写入数据
buffer.put("Hello NIO".getBytes());
System.out.println("\n===== 写入后 =====");
System.out.println("position: " + buffer.position()); // 9
System.out.println("limit: " + buffer.limit()); // 1024
// 切换为读模式
buffer.flip();
System.out.println("\n===== flip后(读模式)=====");
System.out.println("position: " + buffer.position()); // 0
System.out.println("limit: " + buffer.limit()); // 9
// 读取数据
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("读取内容:" + new String(data)); // Hello NIO
// 重置(可以重新读取)
buffer.rewind();
System.out.println("\n===== rewind后 =====");
System.out.println("position: " + buffer.position()); // 0
// 清空(回到初始状态,可以重新写入)
buffer.clear();
System.out.println("\n===== clear后 =====");
System.out.println("position: " + buffer.position()); // 0
System.out.println("limit: " + buffer.limit()); // 1024
}
}
Buffer核心方法:
| 方法 | 说明 |
|---|---|
allocate(int) |
创建指定容量的缓冲区 |
put() |
写入数据 |
get() |
读取数据 |
flip() |
切换到读模式(limit=position, position=0) |
rewind() |
重置position为0,重新读取 |
clear() |
清空缓冲区,回到初始状态 |
mark() |
标记当前位置 |
reset() |
回到mark的位置 |
2.3 Channel(通道)
java
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
public class ChannelDemo {
// 使用FileChannel复制文件
public static void copyFile(String src, String dest) throws IOException {
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest);
FileChannel srcChannel = fis.getChannel();
FileChannel destChannel = fos.getChannel();
// 方式1:使用transferTo(零拷贝)
srcChannel.transferTo(0, srcChannel.size(), destChannel);
destChannel.close();
srcChannel.close();
fos.close();
fis.close();
}
// 使用FileChannel写入文件
public static void writeFile(String filePath, String content) throws IOException {
FileOutputStream fos = new FileOutputStream(filePath);
FileChannel channel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.wrap(content.getBytes());
channel.write(buffer);
channel.close();
fos.close();
}
// 使用FileChannel读取文件
public static String readFile(String filePath) throws IOException {
FileInputStream fis = new FileInputStream(filePath);
FileChannel channel = fis.getChannel();
ByteBuffer buffer = ByteBuffer.allocate((int) channel.size());
channel.read(buffer);
buffer.flip(); // 切换到读模式
String content = new String(buffer.array());
channel.close();
fis.close();
return content;
}
public static void main(String[] args) throws IOException {
writeFile("D:/test/nio_test.txt", "Hello, NIO Channel!");
System.out.println(readFile("D:/test/nio_test.txt"));
copyFile("D:/test/nio_test.txt", "D:/test/nio_copy.txt");
}
}
三、Files工具类
3.1 Files类简介
java.nio.file.Files 是Java 7引入的文件操作工具类,提供了大量静态方法,大大简化了文件操作。
3.2 文件读写
java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.List;
public class FilesReadWriteDemo {
public static void main(String[] args) throws IOException {
Path path = Paths.get("D:/test/files_demo.txt");
// ====== 写入文件 ======
// 写入字符串
Files.writeString(path, "Hello, Files工具类!", StandardCharsets.UTF_8);
// 写入字节
Files.write(path, "字节写入内容".getBytes(StandardCharsets.UTF_8));
// 写入多行
List<String> lines = List.of("第一行", "第二行", "第三行");
Files.write(path, lines, StandardCharsets.UTF_8);
// 追加写入
Files.write(path, "追加内容".getBytes(),
StandardOpenOption.APPEND);
// ====== 读取文件 ======
// 读取为字符串
String content = Files.readString(path, StandardCharsets.UTF_8);
System.out.println(content);
// 读取为字节数组
byte[] bytes = Files.readAllBytes(path);
// 按行读取
List<String> allLines = Files.readAllLines(path, StandardCharsets.UTF_8);
allLines.forEach(System.out::println);
}
}
3.3 文件操作
java
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.stream.Stream;
public class FilesOperationsDemo {
public static void main(String[] args) throws IOException {
Path source = Paths.get("D:/test/source.txt");
Path target = Paths.get("D:/test/target.txt");
// ====== 文件创建 ======
Files.createFile(source); // 创建文件
Files.createDirectories(Paths.get("D:/test/a/b/c")); // 创建多级目录
// ====== 文件复制 ======
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
// ====== 文件移动/重命名 ======
Files.move(source, Paths.get("D:/test/renamed.txt"),
StandardCopyOption.REPLACE_EXISTING);
// ====== 文件删除 ======
Files.deleteIfExists(target); // 删除文件(不存在不报错)
// ====== 文件判断 ======
Path p = Paths.get("D:/test");
System.out.println("是否存在:" + Files.exists(p));
System.out.println("是否目录:" + Files.isDirectory(p));
System.out.println("是否文件:" + Files.isRegularFile(p));
System.out.println("是否可读:" + Files.isReadable(p));
System.out.println("是否可写:" + Files.isWritable(p));
// ====== 文件属性 ======
System.out.println("文件大小:" + Files.size(p) + " bytes");
// ====== 遍历目录 ======
try (Stream<Path> stream = Files.list(p)) {
stream.forEach(System.out::println);
}
// ====== 递归遍历 ======
System.out.println("\n递归遍历:");
Files.walkFileTree(Paths.get("D:/test"), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
System.out.println(" " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
System.out.println("📁 " + dir);
return FileVisitResult.CONTINUE;
}
});
// ====== 查找文件 ======
try (Stream<Path> found = Files.find(p, 10,
(path2, attrs) -> path2.toString().endsWith(".txt") && attrs.isRegularFile())) {
found.forEach(System.out::println);
}
}
}
四、Path与Paths
4.1 Path接口
java
import java.nio.file.Path;
import java.nio.file.Paths;
public class PathDemo {
public static void main(String[] args) {
// 创建Path
Path path = Paths.get("D:/test/subdir/file.txt");
// 基本操作
System.out.println("文件名:" + path.getFileName()); // file.txt
System.out.println("父路径:" + path.getParent()); // D:\test\subdir
System.out.println("根路径:" + path.getRoot()); // D:\
System.out.println("路径元素数:" + path.getNameCount()); // 3
System.out.println("第0个元素:" + path.getName(0)); // test
System.out.println("绝对路径:" + path.toAbsolutePath());
// 路径拼接
Path basePath = Paths.get("D:/test");
Path resolved = basePath.resolve("subdir/file.txt"); // D:\test\subdir\file.txt
System.out.println("拼接结果:" + resolved);
// 相对路径计算
Path path1 = Paths.get("D:/test/a/b/c");
Path path2 = Paths.get("D:/test/x/y");
Path relative = path1.relativize(path2);
System.out.println("相对路径:" + relative); // ..\..\..\x\y
// 规范化路径
Path messy = Paths.get("D:/test/../test/./subdir/../file.txt");
System.out.println("规范化:" + messy.normalize()); // D:\test\file.txt
}
}
五、try-with-resources
5.1 传统方式的问题
java
// 传统方式:需要手动关闭资源,代码冗长且容易遗漏
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
// 处理文件
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.2 try-with-resources语法
java
import java.io.*;
public class TryWithResourcesDemo {
// 基本用法
public static void readFile(String path) throws IOException {
try (FileReader reader = new FileReader(path);
BufferedReader br = new BufferedReader(reader)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} // 自动关闭reader和br
// 无需写finally块
}
// 多个资源
public static void copyFile(String src, String dest) throws IOException {
try (FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
} // 按声明的逆序自动关闭:bos → bis → fos → fis
}
// 自定义AutoCloseable
public static class DatabaseConnection implements AutoCloseable {
public DatabaseConnection() {
System.out.println("建立数据库连接");
}
public void query(String sql) {
System.out.println("执行SQL:" + sql);
}
@Override
public void close() {
System.out.println("关闭数据库连接");
}
}
public static void main(String[] args) {
try (DatabaseConnection conn = new DatabaseConnection()) {
conn.query("SELECT * FROM users");
} // 自动调用conn.close()
}
}
5.3 try-with-resources与异常处理
java
public class ExceptionHandlingDemo {
public static void main(String[] args) {
// 当try块和close()都抛出异常时
// close()抛出的异常会被"抑制"(suppressed)
try (MyResource resource = new MyResource()) {
resource.doSomething(); // 抛出RuntimeException
} catch (Exception e) {
System.out.println("主异常:" + e.getMessage());
Throwable[] suppressed = e.getSuppressed();
for (Throwable t : suppressed) {
System.out.println("被抑制的异常:" + t.getMessage());
}
}
}
static class MyResource implements AutoCloseable {
void doSomething() {
throw new RuntimeException("业务异常");
}
@Override
public void close() {
throw new RuntimeException("关闭异常");
}
}
}
六、实战:文件操作工具类
java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 文件操作工具类
*/
public class FileUtils {
/**
* 读取文本文件所有内容
*/
public static String readText(String filePath) throws IOException {
return Files.readString(Path.of(filePath), StandardCharsets.UTF_8);
}
/**
* 读取文本文件为行列表
*/
public static List<String> readLines(String filePath) throws IOException {
return Files.readAllLines(Path.of(filePath), StandardCharsets.UTF_8);
}
/**
* 写入文本文件
*/
public static void writeText(String filePath, String content) throws IOException {
Files.writeString(Path.of(filePath), content, StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
/**
* 追加文本到文件
*/
public static void appendText(String filePath, String content) throws IOException {
Files.writeString(Path.of(filePath), content, StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
}
/**
* 复制文件
*/
public static void copy(String source, String target) throws IOException {
Files.copy(Path.of(source), Path.of(target), StandardCopyOption.REPLACE_EXISTING);
}
/**
* 移动文件
*/
public static void move(String source, String target) throws IOException {
Files.move(Path.of(source), Path.of(target), StandardCopyOption.REPLACE_EXISTING);
}
/**
* 删除文件
*/
public static boolean delete(String filePath) throws IOException {
return Files.deleteIfExists(Path.of(filePath));
}
/**
* 获取文件大小(字节)
*/
public static long size(String filePath) throws IOException {
return Files.size(Path.of(filePath));
}
/**
* 查找目录中指定扩展名的文件
*/
public static List<Path> findFiles(String dir, String extension) throws IOException {
try (Stream<Path> stream = Files.walk(Path.of(dir))) {
return stream.filter(p -> p.toString().endsWith(extension))
.filter(Files::isRegularFile)
.collect(Collectors.toList());
}
}
/**
* 统计目录下文件数量
*/
public static long countFiles(String dir) throws IOException {
try (Stream<Path> stream = Files.walk(Path.of(dir))) {
return stream.filter(Files::isRegularFile).count();
}
}
/**
* 计算目录总大小
*/
public static long dirSize(String dir) throws IOException {
try (Stream<Path> stream = Files.walk(Path.of(dir))) {
return stream.filter(Files::isRegularFile)
.mapToLong(p -> {
try { return Files.size(p); }
catch (IOException e) { return 0; }
})
.sum();
}
}
}
七、常见面试题解析
Q1:序列化和反序列化有什么作用?
序列化用于将对象状态保存到文件或通过网络传输。常见场景:Session持久化、RPC远程调用、缓存序列化、深拷贝等。
Q2:transient关键字的作用是什么?
transient修饰的字段不会参与序列化。适用于敏感信息(密码)或不必要序列化的字段。
Q3:NIO和传统IO有什么区别?
NIO面向缓冲区而非流,支持非阻塞IO和多路复用。传统IO是阻塞式的,一个连接需要一个线程。NIO适合高并发场景。
Q4:try-with-resources的原理是什么?
编译器会自动在finally块中调用资源的close()方法,且按声明的逆序关闭。如果try块和close()都抛异常,close()的异常会被"抑制"。
八、总结与下篇预告
本篇要点
| 要点 | 说明 |
|---|---|
| 序列化 | ObjectInputStream/ObjectOutputStream实现对象持久化 |
| NIO | Buffer + Channel,面向缓冲区的高效IO |
| Files | 文件操作的瑞士军刀,静态方法一行搞定 |
| Path | 文件路径的抽象表示 |
| try-with-resources | 自动资源管理,告别繁琐的finally |
📢 下篇预告
第26篇:多线程基础------我们将学习Thread、Runnable、Callable,了解线程状态和线程安全问题!
💬 互动话题
你在项目中用过NIO吗?Files工具类和传统IO相比,你觉得哪个更好用?欢迎在评论区交流!
参考资料: