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


参考资料

相关推荐
阿演1 小时前
DataDjinn 新版本更新:新增 Oracle 支持,查询窗口、表预览和连接树继续打磨
数据库·oracle·ai编程·数据库连接工具
小研说技术1 小时前
Spring AI实现rag流程(简易版)
java·后端
亓才孓1 小时前
【本地项目引用外部库的类,想修改字段遇到的请缓存的问题】
java·maven
小林敲代码77881 小时前
记录一下IDEA中很多变量变色的方案
java·开发语言·spring boot·idea
lixora1 小时前
Oracle 11g Active Data Guard Go 自动化部署工具 v1.0
数据库·oracle
Nturmoils1 小时前
自增主键别只会 auto_increment,先把值从哪来讲清楚
数据库·后端
南知意-1 小时前
IDEA 2026.1最新版安装教程
java·ide·intellij-idea·idea安装·idea激活
叶小鸡1 小时前
Java 篇-项目实战-AI 天机学堂(从 0 到 1)-day5
数据库·redis·缓存
星子落怀aa2 小时前
Java 反复报错?Gemini助力修复
java