美文网首页
Java序列和反序列,看这一篇就够了

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

作者: moutory | 来源:发表于2021-05-07 15:42 被阅读0次

前言

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);
    }
transient关键字使用

我们可以看到,此时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;
    ...
}
步骤三:执行反序列化,查看结果
未定义serialVersionUID的结果

我们可以看到,此时反序列化的时候代码报错了,说反序列化得到的序列话版本号和当前类的序列版本号不一致,无法反序列化成功。本质上是因为没有显式地指明序列化版本号的前提下,如果修改了类的内容,那么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’
开启idea中的生成序列化版本功能 使用idea自带的序列号版本生成工具生成版本号 生成结果

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

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

相关文章

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

    前言 java的序列化和反序列化内容是java学习的基础之一,java的序列化常见于网络中的对象传输以及内存对象持...

  • java专题之序列化

    一、基本概念 1、什么是序列化和反序列化 (1)Java序列化是指把Java对象转换为字节序列的过程,而Java反...

  • 序列化

    java序列化,看这篇就够了https://juejin.im/post/5ce3cdc8e51d45777b1a...

  • Java序列化机制

    原文连接:java序列化,看这篇就够了_一个天秤座的架构师-CSDN博客[https://blog.csdn.ne...

  • 金融相关时间序列分析全指南

    来源:yv.l1.pnn - kesci.com原文链接:时间序列分析入门,看这一篇就够了 点击以上链接? 不用配...

  • Java-序列化-反序列化

    Thanks Java基础学习总结——Java对象的序列化和反序列化java序列化反序列化原理Java 序列化的高...

  • JDK 序列化

    序列化和分序列化概念 什么是序列化和反序列化 Java序列化是指把Java对象转换为字节序列的过程,而Java反序...

  • JAVA反序列化漏洞

    目录 反序列化漏洞序列化和反序列化JAVA WEB中的序列化和反序列化对象序列化和反序列范例JAVA中执行系统命令...

  • Java基础-序列化与反序列化

    序列化和反序列化在面试中也经常考查,下面就总结一下 Java 中的序列化和反序列化。 什么是序列化和反序列化? 序...

  • Java序列化

    Java序列化的几种方式以及序列化的作用 Java基础学习总结——Java对象的序列化和反序列化

网友评论

      本文标题:Java序列和反序列,看这一篇就够了

      本文链接:https://www.haomeiwen.com/subject/relbqktx.html