深入理解Java原生的序列化机制

概念

一个对象如果想在硬盘上存储,一定就需要借助于一定的数据格式。这种把对象转换为硬盘存储的格式的过程就叫做对象的序列化,同样地,将这些文件再反向转换为程序中对象的操作就叫做反序列化
一些复杂的解决方案可能是将对象转换为json字符串的方式,这种方式的优点是易读,但是效率还是太低,所以Java的序列化的解决方案是将对象转换为一个二进制流的形式,来实现数据的持久化,本篇文章将会来详细讲解序列化的实现和原理

实现

准备

我们这里有一个普通的对象,要注意的是这个类和其中用到的所有对象都需要实现序列化接口Serializable:

class Demo implements Serializable {
int val = 10;
String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
A a = new A(20);
@Override
public String toString() {
return "[hashcode=" + hashCode() + " val=" + val + ", time=" + time
+ ", A.val=" + a.val +"]";
}
}

这个A是一个普通的对象,如下:

class A implements Serializable {
int val = 20;
public A(int val) {
this.val = val;
}
}

现在我们有一个Demo对象,来输出一下这个对象的标志字符串:

Demo demo = new Demo();
System.out.println(demo.toString());

输出结果:

[hashcode=1625635731 val=10, time=20:28:56, A.val=20]

序列化

现在,我们需要将这个对象序列化为二进制流,则需要以下的操作:

FileOutputStream fileOutputStream = new FileOutputStream("target");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(demo);
objectOutputStream.flush();
objectOutputStream.close();

这样,demo对象就被我们持久化到硬盘的target文件中了

反序列化

反之,如果我们想将这个对象从target文件中取出,就需要如下的操作:

FileInputStream fileInputStream = new FileInputStream("target");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Demo newDemo = (Demo)objectInputStream.readObject();

检验

现在,我们用以下的语句来检验这两个对象是否是一个对象:

System.out.println(newDemo.toString());
System.out.println("demo == newDemo : " + (demo == newDemo));

输出

[hashcode=885284298 val=10, time=20:28:56, A.val=20]
demo == newDemo : false

我们会发现,反序列化得到的对象虽然值和原有对象一致,但是其不是同一个对象,这一点很重要

原理

我们打开序列化生成的target文件,这里需要用二进制流的方式打开:

这里可以将文件分为5个部分:

  • 文件头:声明文件是一个对象序列化文件,同时声明了序列化版本
  • 类描述:声明类信息,包括类名、序列化id,以及域的个数等属性
  • 属性描述
  • 父类信息描述
  • 对象属性的实际值

也就是说,在这个二进制文件中,通过这几部分就能表明一个类的全部信息,在反序列化的过程中,Java将会按照指定的文件格式来从文件中恢复数据

注意事项

序列化的类一定要实现Serializable接口
序列化类中包含的自定义对象都需要实现Serializable接口

这两点是为什么呢,我们来看ObjectOutputStream中的writeObject0方法,这里截取了一小段:

if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}

这段代码中的obj不仅仅是被序列化的对象,还会是这个对象中的所有字段,也就是说其中的域对象,必须是字符串、数组、枚举和序列化接口中的一种,否则就会抛出异常

序列化ID

其实,还有一点注意事项,我留在了这里来讲:

在序列化和反序列化之间,对象的字段名称、类型和数量均不能改变

这是为什么呢,我们来看反序列化中的一块代码:

if (model.serializable == osc.serializable &&
!cl.isArray() &&
suid != osc.getSerialVersionUID()) {
throw new InvalidClassException(osc.name,
"local class incompatible: " +
"stream classdesc serialVersionUID = " + suid +
", local class serialVersionUID = " +
osc.getSerialVersionUID());
}

这是ObjectStreamClass中的initNonProxy方法中的一段,这个方法也就是读取我们序列化文件的核心方法,用于初始化类描述符

不过我们重点不在这里,重点是一个suid和osc.getSerialVersionUID()的比较,这时候就要涉及到一个序列化id的概念了,序列化id的声明类似下面这种形式:

class Demo implements Serializable {
// 这个序列化id一般的ide都会提供有自动生成的插件,感兴趣的可以自行下载
private static final long serialVersionUID = -5809782578272943999L;
// ...
}

Java的反序列化成功与否的关键,就是比较文件的序列化id和类的序列化id是否一致,如果一致,则认为文件中的对象和类对象是同一个对象,否则,就说明两个类压根就不是一个类,如果强行转换则很有可能发生异常

但是我们之前没有手动设置序列化id也一样能反序列化成功不是吗?其实,之前能反序列化成功仅仅是因为我们没有改动原来的类,如果我们没有设置序列化id,则以下任何的操作,均会导致反序列化失败:

  • 修改了字段/方法的名称/类型
  • 添加或删除字段/方法

看到了吗,即使我们仅仅修改了字段的名称,也会导致反序列化的失败,如果不注意这一点,将会导致所有反序列化操作的崩溃,但是只要我们设置一个序列化id,即使我们把类中元素删的一干二净,也一样会反序列化成功,只不过是丢失属性而已

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Java 序列化与反序列化(Serialization)

    一.什么是?为什么需要? 序列化(Serialization)是将对象的状态信息转化为可以存储或者传输的形式的过程,反序列化则为其逆过程. 内存的易失性:传输需要:一些应用场景中需要将对象持久化下来,以便在需要的时候进行读取. 二.JDK提供的API java.io.ObjectOutputStream类的 writeObject(Object obj)方法 java.io.ObjectInputStream类的readObject()方法 对于Serializable,如果没有重写 write

  • Java序列化与反序列化的实例分析讲解

    序列化与反序列化 Java对象是有生命周期的,当生命周期结束它就会被回收,但是可以通过将其转换为字节序列永久保存下来或者通过网络传输给另一方. 把对象转换为字节序列的过程称为对象的序列化:把字节序列恢复为对象的过程称为对象的反序列化. Serializable接口 一个类实现java.io.Serializable接口就可以被序列化或者反序列化.实际上,Serializable接口中没有任何变量和方法,它只是一个标识.如果没有实现这个接口,在序列化或者反序列化时会抛出NotSerializabl

  • java中对象的序列化与反序列化深入讲解

    引言: 序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化.可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间.序列化是为了解决在对对象流进行读写操作时所引发的问题. 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是Web服务器中的Session对 象,当有 10万用户并发访问,就有可能出现10万个Session对

  • 实例分析java对象的序列化和反序列化

    引言: 序列化是将对象的状态信息转换为可以存储或传输的形式的过程,在序列化期间,对象将其带你过去的状态写入到临时或持储存区,反序列化就是重新创建对象的过程,此对象来自于临时或持久储存区. 序列化的作用: 就好比如存储数据到数据库,将一些数据持久化到数据库中,而有时候需要将对象持久化,虽然说将对象状态持久化的方式有很多,但是java给我们提供了一种很便捷的方式,那就是序列化,序列化可以实现对象到文件之间的直接转换,实现细节对我们隐藏. 具体的三种用途: •将对象的状态信息持久化保存到硬盘上 •将对

  • 详解java中的深拷贝和浅拷贝(clone()方法的重写、使用序列化实现真正的深拷贝)

    1.序列化实现 public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T object){ T cloneObj = null; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new Objec

  • 深入理解Java原生的序列化机制

    概念 一个对象如果想在硬盘上存储,一定就需要借助于一定的数据格式.这种把对象转换为硬盘存储的格式的过程就叫做对象的序列化,同样地,将这些文件再反向转换为程序中对象的操作就叫做反序列化 一些复杂的解决方案可能是将对象转换为json字符串的方式,这种方式的优点是易读,但是效率还是太低,所以Java的序列化的解决方案是将对象转换为一个二进制流的形式,来实现数据的持久化,本篇文章将会来详细讲解序列化的实现和原理 实现 准备 我们这里有一个普通的对象,要注意的是这个类和其中用到的所有对象都需要实现序列化接

  • 深入理解Java中的SPI机制

    本文通过探析JDK提供的,在开源项目中比较常用的Java SPI机制,希望给大家在实际开发实践.学习开源项目提供参考. 1 SPI是什么 SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件. 整体机制图如下: Java SPI 实际上是"基于接口的编程+策略模式+配置文件"组合实现的动态加载机制. 系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接

  • 图文详解Java中的序列化机制

    目录 概述 对象序列化和反序列化机制 修改默认的序列化机制 使用transient关键字 自定义readObject.writeObject方法 实现Externalizable接口 serialVersionUID的作用 使用序列化clone 概述 java中的序列化可能大家像我一样都停留在实现Serializable接口上,对于它里面的一些核心机制没有深入了解过.直到最近在项目中踩了一个坑,就是序列化对象添加一个字段以后,使用方系统报了反序列化失败,原因是我们双方的序列化对象没有加上seri

  • 全面理解java中的异常处理机制

    一.java异常总结: 异常就是程序运行时出现不正常运行情况 1.异常由来: 通过java的类的形式对现实事物中问题的描述,并封住成了对象 其实就是java对不正常情况描述后的对象体现 2.对于问题的划分有两种:一种是严重的问题,一种是非严重的问题 对于严重的,java通过Error类来描述 对于Error一般不编写针对性的代码对其进行处理 对于非严重的,java通过Exception类来描述 对于Exception可以使用针对性的处理方式进行处理 3.常见的异常有:数组角标越界异常,空指针异常

  • 深入理解java中的拷贝机制

    前言 众所周知在Java中,拷贝分为深拷贝和浅拷贝两种.java在公共超类Object中实现了一种叫做clone的方法,这种方法clone出来的新对象为浅拷贝,而通过自己定义的clone方法为深拷贝. (一)Object中clone方法 如果我们new出一个新对象,用一个声明去引用它,之后又用另一个声明去引用前一个声明,那么最后的结果是:这两个声明的变量将指向同一个对象,一处被改全部被改.如果我们想创建一个对象的copy,这个copy和对象的各种属性完全相同,而且修改这个copy和原对象毫无关系

  • 简单理解Java的垃圾回收机制与finalize方法的作用

    垃圾回收器要回收对象的时候,首先要调用这个类的finalize方法(你可以 写程序验证这个结论),一般的纯Java编写的Class不需要重新覆盖这个方法,因为Object已经实现了一个默认的,除非我们要实现特殊的功能(这 里面涉及到很多东西,比如对象空间树等内容). 不过用Java以外的代码编写的Class(比如JNI,C++的new方法分配的内存),垃圾回收器并不能对这些部分进行正确的回收,这时就需要我们覆盖默认的方法来实现对这部分内存的正确释放和回收(比如C++需要delete). 总之,f

  • 理解Java当中的回调机制(翻译)

    你好,今天我要和大家分享一些东西,举例来说这个在JavaScript中用的很多.我要讲讲回调(callbacks).你知道什么时候用,怎么用这个吗?你真的理解了它在java环境中的用法了吗?当我也问我自己这些问题,这也是我开始研究这些的原因.这个背后的思想是控制反转( PS:维基百科的解释是控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度.)这个范例描述了框架(framework)的工作方式,也以"好莱坞原则:

  • 由ArrayList来深入理解Java中的fail-fast机制

    1. fail-fast简介 "快速失败"也就是fail-fast,它是Java集合的一种错误检测机制.某个线程在对collection进行迭代时,不允许其他线程对该collection进行结构上的修改. 例如:假设存在两个线程(线程1.线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生f

  • 深入理解Java对象的序列化与反序列化的应用

    当两个进程在进行远程通信时,彼此可以发送各种类型的数据.无论是何种类型的数据,都会以二进制序列的形式在网络上传送.发送方需要把这个Java对象转换为字节序列,才能在网络上传送:接收方则需要把字节序列再恢复为Java对象. 把Java对象转换为字节序列的过程称为对象的序列化.把字节序列恢复为Java对象的过程称为对象的反序列化.对象的序列化主要有两种用途:1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中:2) 在网络上传送对象的字节序列.一. JDK类库中的序列化APIjava.io

  • 深入理解Java定时调度(Timer)机制

    简介 在实现定时调度功能的时候,我们往往会借助于第三方类库来完成,比如: quartz . Spring Schedule 等等.JDK从1.3版本开始,就提供了基于 Timer 的定时调度功能.在 Timer 中,任务的执行是串行的.这种特性在保证了线程安全的情况下,往往带来了一些严重的副作用,比如任务间相互影响.任务执行效率低下等问题.为了解决 Timer 的这些问题,JDK从1.5版本开始,提供了基于 ScheduledExecutorService 的定时调度功能. 本节我们主要分析 T

随机推荐