Java序列和反序列,看这一篇就够了

前言

java的序列化和反序列化内容是java学习的基础之一,java的序列化常见于网络中的对象传输以及内存对象持久化,本篇文章将主要对java中如何使用序列化,transient关键字作用和序列化中的serialVersionUID的作用三个部分的内容进行讲解,希望对这方面不熟悉的读者有所帮助。

一、什么是序列化(反序列化)

我们不妨设想这样一个场景,假如你用java开发了一款格斗类游戏,角色可以随着通关不断升级和获取装备,运行起来似乎也很不错,但你发现角色的数据都是存储在内存中的,一旦电脑关机或者结束掉游戏后,后面再打开游戏的时候角色的数据都丢失了,不得不需要重头开始玩。
上述的问题本质上是因为数据存储在内存中,而没有持久化到硬盘中,如果能够实现这一点,那么上述的问题也就迎刃而解。
在Java中,序列化和反序列化就能够帮助我们来实现这种需求。

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

序列化除了可以将内存中的对象持久化到硬盘上之外,主要还用于网络中的对象字节序列传输。

在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

二、在代码中实现序列化和反序列化

定义POJO类
public class Hero implements Serializable {
    // 角色名称
    private String name;
    // 角色等级
    private String level;
    // 攻击力
    private Integer damage;
    // 生命值
    private Integer healthPoint;
    // 最后一次停留点
    private String lastPoint;
    ...
}

需要注意的是,需要序列化的类需要实现Serializable 接口,表明该类可以支持序列化。

(一)序列化
定义测试类
public class SerializableTest {

    public static void main(String[] args) throws Exception {
        // 创建角色
        Hero hero = new Hero("战士","5",150,3000,"新手村");
        serializeHero(hero);
    }

    /**
     * 序列化英雄角色到硬盘中
     */
    public static void serializeHero(Hero hero) throws IOException {
     // 创建对象输出流,并指定输出的位置
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\MyCode\\hero.txt")));
        // 将对象输出到本地文本中
        oos.writeObject(hero);
        System.out.println("序列化结束...");
        // 关闭输出流
        oos.close();
    }
}

在序列化方法中,我们通过ObjectOutputStream对象将Hero对象持久化到硬盘中,我们可以在本地的文件夹中找到这份文件。

(二)反序列化

我们可以用ObjectOutputStream对象将硬盘中的字节序列重新转换为内存中的对象:

public class SerializableTest {

    public static void main(String[] args) throws Exception {
        // 创建角色
        Hero hero = deSerializeHero("D:\\MyCode\\hero.txt");
        System.out.println(hero);
    }

    /**
     * 从硬盘中读取角色数据到内存中
     * @return
     */
    public static Hero deSerializeHero(String filename) throws IOException, ClassNotFoundException {
        // 创建对象输入流,根据指定的文件位置读取数据
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(filename)));
        Hero hero = (Hero)ois.readObject();
        System.out.println("反序列化结束...");
        // 关闭输入流
        ois.close();
        return hero;
    }
}

反序列化的结果如下,我们可以看到对象被成功的读取到内存中了


三、transient关键字的使用

讲到这里,我们现在已经可以实现游戏中的存档功能了,但需要存档的时候,我们只需要将角色的数据持久化到硬盘中,等下次游戏的时候再通过反序列化重新读取到内存中即可。
但这里有一个小问题,在实际的角色设计中,需要被序列化的类中可能有大量的属性,有些属性比如HP值,等级这些属性是必须要记录的,但有些属性比如角色说过的话,角色的快捷键自定义之类的信息,有些是不太重要,可以不需要持久化的。如果将这些非必要的字段排除在外的话,可以更高效地实现对象的序列化和反序列化工作。
java中为我们提供了transient关键字来实现排除序列化的范围中的指定字段
以上面的例子为基础,我们看如何使用transient关键字来实现Hero对象中lastPoint属性的忽略序列化。

步骤一:给lastPoint属性加上transient关键字进行修饰
public class Hero implements Serializable {
    // 角色名称
    private String name;
    // 角色等级
    private String level;
    // 攻击力
    private Integer damage;
    // 生命值
    private Integer healthPoint;
    // 最后一次停留点
    private transient String lastPoint;
    ...
}
步骤二:将对象序列化到本地后,再反序列化看对象的结果:
    public static void main(String[] args) throws Exception {
        // 创建角色
        //Hero hero = new Hero("战士","5",150,3000,"新手村");
        //serializeHero(hero);
        Hero hero = deSerializeHero("D:\\MyCode\\hero.txt");
        System.out.println(hero);
    }

我们可以看到,此时Hero对象的lastPoint属性是空的。

四、SerialVersionUID的作用

相信在网上很多讲解Java序列化(反序列化)的文章中,都有提到关于显式地写明SerialVersionUID的作用,要求凡是实现Serializable接口的类中最好都有一个表示序列化版本标识符的静态变量serialVersionUID,那么这个SerialVersionUID到底是什么呢?

我们不妨先来想一个问题:java中是如何确定反序列化获得的对象就是我们想要的对象呢?

其实它依靠的就是这个SerialVersionUID来确定的。那这样就又有一个问题了,我们在之前的案例中,并没有在Hero类中指定系列版本ID,为什么反序列还可以成功呢?
这是因为没有指定Customer类的serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件 多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。
我们可以试验一下,我们先序列化对象,然后给实体类中加入weight属性,看能不能反序列化成功。

步骤一:系列化对象(过程忽略)
步骤二:给实体类对象加入新的属性
public class Hero implements Serializable {
    ... 
    // 角色体重
    private Double weight;
    ...
}
步骤三:执行反序列化,查看结果

我们可以看到,此时反序列化的时候代码报错了,说反序列化得到的序列话版本号和当前类的序列版本号不一致,无法反序列化成功。本质上是因为没有显式地指明序列化版本号的前提下,如果修改了类的内容,那么java自动生成的序列化版本号就会自动发生变化,而后无法和反序列化得到的对象相对应。

既然声明序列化版本号这么重要,那我们可以怎么样来进行声明呢?
常见的声明方式有两种:
(1)使用默认的serialVersionUID生成方式
public class Hero implements Serializable {

    private static final long serialVersionUID = 1L;

     ...
}
(2)使用IDEA中自带的serialVersionUID生成器来生成

笔者用的是intellij idea所以这里的话是以这款IDEA来进行序列化版本号生成方式来介绍的:

Setting -> Editor ->Inspections-> Java -> Serialization issues->Serializable class without ’serialVersionUID’

通过这两种方法中的任意一种,我们就可以实现多版本实体类之间的反序列化兼容了。
至此,本篇文章对于java中的序列和反序列化内容就讲解结束了。在实际应用中,序列化除了应用在数据持久化上,还应用在网络通信上,虽然用途不同,但实际的原理是一样的,更多细节大家可以自己再去研究一下。有疑问也欢迎留言。

参考文章:
Java基础学习总结——Java对象的序列化和反序列化

posted @   moutory  阅读(43)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示