Java中序列化与反序列化的特性解读

 更新时间:2023年08月10日 09:21:27   作者:ycfxhsw  
这篇文章主要介绍了Java中序列化与反序列化的特性解读,当我们需要将内存中的对象持久化到磁盘,数据库中时, 当我们需要与浏览器进行交互时,当我们需要实现 RPC 时, 这个时候就需要序列化和反序列化了,需要的朋友可以参考下

一、序列化与反序列化

  • 序列化:把对象转换为字节序列的过程称为对象的序列化。
  • 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

二、什么时候需要用到序列化和反序列化

当我们只在本地 JVM 里运行下 Java 实例,这个时候是不需要什么序列化和反序列化的,但当我们需要将内存中的对象持久化到磁盘,数据库中时, 当我们需要与浏览器进行交互时,当我们需要实现 RPC 时, 这个时候就需要序列化和反序列化了。

前两个需要用到序列化和反序列化的场景, 是不是让我们有一个很大的疑问? 我们在与浏览器交互时,还有将内存中的对象持久化到数据库中时,好像都没有去进行序列化和反序列化, 因为我们都没有实现 Serializable 接口, 但一直正常运行。

下面先给出结论:

只要我们对内存中的对象进行持久化或网络传输, 这个时候都需要序列化和反序列化.

理由:

服务器与浏览器交互时真的没有用到 Serializable 接口吗?JSON 格式实际上就是将一个对象转化为字符串, 所以服务器与浏览器交互时的数据格式其实是字符串,我们来看来 String 类型的源码:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    /** Cache the hash code for the string */
    private int hash; // Default to 0
    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

String 类型实现了 Serializable 接口,并显示指定 serialVersionUID 的值.

然后我们再来看对象持久化到数据库中时的情况, Mybatis 数据库映射文件里的 insert 代码:

<insert id="insertUser" parameterType="com.ycfxhsw.User">
    INSERT INTO t_user(name, age) VALUES (#{name}, #{age})
</insert>

实际上我们并不是将整个对象持久化到数据库中, 而是将对象中的属性持久化到数据库中, 而这些属性都是实现了 Serializable 接口的基本属性.

三、为什么要实现 Serializable 接口?

在 Java 中实现了 Serializable 接口后, JVM 会在底层帮我们实现序列化和反序列化, 如果我们不实现 Serializable 接口, 那自己去写一套序列化和反序列化代码也行。

四、为什么还要指定 serialVersionUID 的值?

如果不显示指定 serialVersionUID, JVM 在序列化时会根据属性自动生成一个 serialVersionUID, 然后与属性一起序列化,再进行持久化或网络传输。

在反序列化时,JVM 会再根据属性自动生成一个新版 serialVersionUID,然后将这个新版 serialVersionUID 与序列化时生成的旧版 serialVersionUID 进行比较,如果相同则反序列化成功, 否则报错.

如果显示指定了 serialVersionUID, JVM 在序列化和反序列化时仍然都会生成一个 serialVersionUID, 但值为我们显示指定的值,这样在反序列化时新旧版本的 serialVersionUID 就一致了.

在实际开发中, 不显示指定 serialVersionUID 的情况会导致什么问题?如果我们的类写完后不再修改,那当然不会有问题。

但这在实际开发中是不可能的,我们的类会不断迭代,一旦类被修改了,那旧对象反序列化就会报错。所以在实际开发中, 我们都会显示指定一个 serialVersionUID,值是多少无所谓, 只要不变就行。

写个实例测试下:

(1) User 类

  • 不显示指定 serialVersionUID.
import java.io.Serializable;
public class User implements Serializable {
    String name;
    Integer age;
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
	// 省略 get/set 方法
}

(2) 测试类

先进行序列化, 再进行反序列化.

public class SerializeTest {
    private static File file = new File("D:/Desktop/user.txt");
    private static void serialize(User user) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        oos.writeObject(user);
        oos.close();
    }
    private static User deserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        return (User) ois.readObject();
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        User user = new User();
        user.setName("织女");
        user.setAge(18);
        System.out.println("序列化前的结果为:" + user);
        serialize(user);
        User user1 = deserialize();
        System.out.println("反序列化后的结果为:" + user1);
    }
}

序列化前的结果为:User{name=‘织女’, age=18}

反序列化后的结果为:User{name=‘织女’, age=18}

(3) 结果

先注释掉反序列化代码, 执行序列化代码, 然后 User 类新增一个属性 sex

public class User implements Serializable {
    String name;
    Integer age;
    String sex;
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
	// // 省略 get/set 方法
}

再注释掉序列化代码执行反序列化代码, 最后结果如下:

local class incompatible: stream classdesc serialVersionUID = -8867211101605543804, local class serialVersionUID = 6343365699042275217

报错结果为序列化与反序列化产生的 serialVersionUID 不一致。

接下来我们在上面 User 类的基础上显示指定一个 serialVersionUID

private static final long serialVersionUID = 1L;

再执行上述步骤, 测试结果如下:

序列化前的结果为:User{name=‘织女’, age=18} 反序列化后的结果为:User{name=‘织女’, age=18, sex=‘null’}

显示指定 serialVersionUID 后就解决了序列化与反序列化产生的 serialVersionUID 不一致的问题。

五、Java 序列化的其他特性

先说结论, 被 transient 关键字修饰的属性不会被序列化, static 属性也不会被序列化.

我们来测试下这个结论:

(1) User 类

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private Integer age;
    private transient String sex;
    private static String signature = "你眼中的世界就是你自己的样子";
    @Override
    public String toString() {
        return "User{" +
                " + name + '\\'' +
                ", age=" + age +
                ", sex='" + sex +'\\'' +
                ", signature='" + signature + '\\'' +
                '}';
    }
    // 省略 get/set 方法
}

(2) 测试类

public class SerializeTest {
    private static File file = new File("D:/Desktop/user.txt");
    private static void serialize(User user) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        oos.writeObject(user);
        oos.close();
    }
    private static User deserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        return (User) ois.readObject();
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        User user = new User();
        user.setName("织女");
        user.setAge(18);
        user.setSex("woman");
        System.out.println("序列化前的结果为:" + user);
        serialize(user);
        User user1 = deserialize();
        System.out.println("反序列化后的结果为:" + user1);
    }
}

(3) 结果

先注释掉反序列化代码, 执行序列化代码, 然后修改 User 类 signature = “我的眼里只有你”,再注释掉序列化代码执行反序列化代码, 最后结果如下:

序列化前的结果: User{name=‘织女’, age=18, sex=‘woman’, signature=‘你眼中的世界就是你自己的样子’}

反序列化后的结果: User{name=‘织女’, age=18, sex=‘null’, signature=‘我的眼里只有你’}

六、static 属性为什么不会被序列化?

因为序列化是针对对象而言的,而 static 属性优先于对象存在, 随着类的加载而加载, 所以不会被序列化.

看到这个结论, 是不是有人会问, serialVersionUID 也被 static 修饰, 为什么 serialVersionUID 会被序列化?

其实 serialVersionUID 属性并没有被序列化, JVM 在序列化对象时会自动生成一个 serialVersionUID, 然后将我们显示指定的 serialVersionUID 属性值赋给自动生成的 serialVersionUID。

到此这篇关于Java中序列化与反序列化的特性解读的文章就介绍到这了,更多相关Java序列化与反序列化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • springboot整合RabbitMQ发送短信的实现

    springboot整合RabbitMQ发送短信的实现

    本文会和SpringBoot做整合,实现RabbitMQ发送短信,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • 详述IntelliJ IDEA插件的安装及使用方法(图解)

    详述IntelliJ IDEA插件的安装及使用方法(图解)

    本篇文章主要介绍了详述 IntelliJ IDEA 插件的安装及使用方法(图解),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • Java多线程之线程的创建

    Java多线程之线程的创建

    这篇文章主要介绍了Java多线程之线程的创建,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • Java实现Socket的TCP传输实例

    Java实现Socket的TCP传输实例

    这篇文章主要介绍了Java实现Socket的TCP传输,实例分析了java通过socket实现TCP传输的相关技巧,需要的朋友可以参考下
    2015-05-05
  • java获取当前日期和时间的二种方法分享

    java获取当前日期和时间的二种方法分享

    这篇文章主要介绍了java获取当前日期和时间的二种方法,需要的朋友可以参考下
    2014-03-03
  • MyBatis-Plus实现连表查询的方法实例

    MyBatis-Plus实现连表查询的方法实例

    这篇文章主要给大家介绍了关于MyBatis-Plus实现连表查询的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-01-01
  • 浅谈Spring Security LDAP简介

    浅谈Spring Security LDAP简介

    这篇文章主要介绍了浅谈Spring Security LDAP简介,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • json解析时遇到英文双引号报错的解决方法

    json解析时遇到英文双引号报错的解决方法

    下面小编就为大家分享一篇json解析时遇到英文双引号报错的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-02-02
  • 浅谈为什么要使用mybatis的@param

    浅谈为什么要使用mybatis的@param

    这篇文章主要介绍了浅谈为什么要使用mybatis的@param,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • Java编程基础元素-运算符

    Java编程基础元素-运算符

    这篇文章主要介绍了Java编程基础元素-运算符,运算符就是在用变量或常量进行运算时,经常需要用到的运算符,Java 提供了丰富的运算符,可分为算术运算符、关系运算符、逻辑运算符和位运算符,下面来看具体的内容介绍吧
    2022-01-01

最新评论