深入理解Java序列化:从入门到实践

在前面的学习中我们简单的学习到了对象流的使用,我们先来回顾一下

对象流

在Java中,对象流是一种特殊的输入输出流,用于处理对象的序列化和反序列化操作。对象流主要包括ObjectOutputStream和ObjectInputStream两个类。

ObjectOutputStream:

这是一个输出流,用于将对象序列化后写入到目标输出流中。它可以将Java对象转换为一系列字节,并写入到底层的输出流(如文件、网络套接字等)中。通过调用writeObject(Object obj)方法,可以将指定的对象序列化后写入流中。

ObjectInputStream:

这是一个输入流,用于从源输入流中读取字节序列,并将其反序列化为Java对象。它允许从文件、网络等源中读取经过序列化的对象数据,并将其恢复为原始的对象形式。通过调用readObject()方法,可以从流中读取并返回下一个对象

序列化与反序列化

序列化

序列化是指将对象的状态信息转换为可以存储或传输的形式(如字节序列)的过程。在Java中,这通常是通过实现java.io.Serializable接口来完成的。当一个对象被序列化时,其状态被保存为一系列字节,这些字节可以被写入到文件、通过网络发送,或者存储在内存中以便稍后使用。序列化后的对象可以被完全重建,以恢复其原始状态。

反序列化

反序列化是序列化的逆过程,即将字节序列转换回Java对象的过程。在Java中,这通常是通过ObjectInputStream类来完成的。当从文件、网络等源中读取到序列化后的对象数据时,可以使用ObjectInputStream的readObject()方法将其反序列化为原始对象。

实践

先先来看看今天的实践案例

向文件中读出对象

当前程序启动后要求用户顺序输入以上四个信息然后实例化一个User对象保存着四个信息并将该对象序列化到文件中。

文件名的命名规则:用户名.obj 比如该用户输入用户名为张三,那么这个对象 序列化后的文件名为:张三.obj

用户输入信息后做必要的格式验证,比如4个信息是否都输入内容了。不能有空的。 用户名只能包含字母数字下划线,并且1-32位

复制代码
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        String name = inputString("请输入你的名字(只能包含字母数字下划线,并且1-32位",
                input -> !input.isEmpty() && input.matches("^[a-zA-Z0-9_]{1,32}$"));//使用这个 Predicate 实例来检查任意字符串是否满足这个条件
        String nick = inputString("请输入你的昵称", input -> !input.isEmpty());
        String psd = inputString("请输入你的密码", input -> !input.isEmpty());
        int age = inputInt("请输入你的年龄", input -> input > 0 && input < 150);
        inputObj(name, psd, nick, age);
    }




    /**
     * @param name
     * @param psd
     * @param nick
     * @param age
     * @throws IOException
     */
    public static void inputObj(String name, String psd, String nick, int age) throws IOException {
        User u = new User(name, psd, nick, age);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(name + ".obj"));
        oos.writeObject(u);
        oos.close();
    }

    /**
     * 判断关于字符串的输入是否合法
     *
     * @param prompt
     * @param validator
     * @return
     */
    public static String inputString(String prompt, Predicate<String> validator) {//定义一个 Predicate<String>,检查字符串
        Scanner scanner = new Scanner(System.in);
        String input;
        while (true) {
            System.out.println(prompt);
            input = scanner.nextLine();
            if (validator.test(input)) {
                return input;
            } else {
                System.out.println("输入有误,请重新输入");
            }
        }
    }

    /**
     * 判断关于int类型的输入是否合法
     *
     * @param prompt
     * @param validator
     * @return
     */
    public static int inputInt(String prompt, Predicate<Integer> validator) {
        Scanner scanner = new Scanner(System.in);
        int input;
        while (true) {
            System.out.println(prompt);
            input = scanner.nextInt();
            if (validator.test(input)) {
                return input;
            } else {
                System.out.println("输入有误,请重新输入");
            }
        }
    }

从文件中写出对象

将当前目录下的所有obj文件获取到,并进行 反序列化后输出每个用户的信息(直接输出反序 列化后的User对象即可)

复制代码
        File dir = new File(".");
        if (dir.isDirectory()) {
            File[] files = dir.listFiles(f -> f.getName().endsWith(".obj"));
            for (int i = 0; i < files.length; i++) {
                File sub=files[i];
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(sub));
                Object obj = ois.readObject();
                User user = (User) obj;
                System.out.println(user);
                ois.close();
            }
        }

注意事项

在开发这个简单的小功能时我还是遇到了许多小问题,总结給大家看看,以后避免犯错

1、流的正确使用 (正确的序列化)

因为一次学习了几个不同的流使用,所以开始我有点混乱,一开始我用PrintWriter来写入文件中导致没有序列化,所以自己打开文件就发现能够正常打开并且里面的内容我都能看懂,其实这是错误的,在使用对象流把对象存入文件中时是要将对象序列化的,因此你正常打开文件其实应该是乱码,必须要反序列化后才能看懂

2、确保 User 类实现了 Serializable 接口

在对象类中必须保证实现Serializable 接口,给这个类打上标签,尽管这个接口中并没有任何的内容,但是这是序列化对象的必要条件。

public class User implements Serializable {

}

3、确保类的稳定性(在写入到读出之间没有改变类的内容)

比如我在最后输出时发现我的user类没有重写toString方法跑去重写后在运行就出现了下面错误

java.io.InvalidClassException

这个异常通常发生在序列化和反序列化对象时,类的 serialVersionUID 不匹配。serialVersionUID 是一个用于版本控制的标识符,它帮助 Java 序列化机制判断两个类的版本是否兼容。

4、流的关闭(资源管理)

记得close你的流

如果不关闭流,可能会导致资源泄露或文件损坏(尤其是在写入模式下)。

就像我没有关闭流,导致写入失败

复制代码
相关推荐
学长毕设9 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
C^h9 小时前
c++ string
开发语言·c++
raindayinrain9 小时前
c++高性能--原子操作,无锁编程,操作顺序
开发语言·c++·性能优化
学逆向的9 小时前
C++ 合并命名空间
开发语言·c++·网络安全
Zhao-Jichao9 小时前
VS2026 打包 C++ 程序生成 EXE,借助 Installer Project 扩展工具
开发语言·c++
zzu123zsw10 小时前
从臃肿的模板调用到声明式契约:深入 UniHttp 动态代理与多渠道生命周期扩展
java·spring boot·microsoft·unihttp
学逆向的10 小时前
C 局部静态变量
c语言·开发语言·网络安全
大都督会赢的11 小时前
C++第一节课
开发语言·c++
“AI国潮设计-小江”11 小时前
【AIGC实战】Python+Stable Diffusion打造“英歌舞财神”IP,解锁“马到成功”等爆款情绪营销文案设计
开发语言·人工智能·python·prompt·aigc
源码集结号11 小时前
云诊所门诊系统源码,基于SpringBoot+Vue.js架构,支持多租户设计与Docker容器化部署
java·spring boot·源码·医院信息系统·门诊系统·诊所系统