25 IO流高级操作——序列化、NIO与Files工具类

目录

  • [🚀 25 IO流高级操作------序列化、NIO与Files工具类](#🚀 25 IO流高级操作——序列化、NIO与Files工具类)

🚀 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相比,你觉得哪个更好用?欢迎在评论区交流!


参考资料

相关推荐
倔强的石头_2 小时前
《Kingbase护城河》——猎捕慢查询:执行计划的微观解析与索引调优实战
数据库
SelectDB4 小时前
Apache Doris Python UDF:让 SQL 直接调用 Python 生态,支撑 Agent 时代复杂业务逻辑
大数据·数据库·python
Flittly5 小时前
【AgentScope Java新手村系列】(16)从RAG到多路检索
java·spring boot·spring
小兔崽子去哪了5 小时前
Java 生成二维码解决方案
java·后端
人活一口气10 小时前
从JVM调优到MCP协议:Java全栈技术体系深度总结与企业级架构实践
java·spring boot
NE_STOP12 小时前
Vibe Coding -- 完整项目案例实操
java
荣码12 小时前
GraphRAG:普通RAG只能回答"点"的问题,我踩了4个坑才搞懂
java·python
SimonKing12 小时前
Google第三方授权登录
java·后端·程序员
明月光81812 小时前
从一行 @Builder 说起:重新拾起 Java 的 Lombok、注解与 Builder 模式
java
考虑考虑21 小时前
Mybatis实现批量插入
java·后端·mybatis