细看Java序列化机制

概况

在程序中为了能直接以 Java 对象的形式进行保存,然后再重新得到该 Java 对象,这就需要序列化能力。序列化其实可以看成是一种机制,按照一定的格式将 Java 对象的某状态转成介质可接受的形式,以方便存储或传输。其实想想就大致清楚基本流程,序列化时将 Java 对象相关的类信息、属性及属性值等等保存起来,反序列化时再根据这些信息构建出 Java 对象。而过程可能涉及到其他对象的引用,所以这里引用的对象的相关信息也要参与序列化。

Java 中进行序列化操作需要实现 Serializable 或 Externalizable 接口。

序列化的作用

  • 提供一种简单又可扩展的对象保存恢复机制。
  • 对于远程调用,能方便对对象进行编码和解码,就像实现对象直接传输。
  • 可以将对象持久化到介质中,就像实现对象直接存储。
  • 允许对象自定义外部存储的格式。

序列化例子

[Java] 纯文本查看 复制代码

?


1

2

3

4

5

FileOutputStream f = new FileOutputStream("tmp.o");

   ObjectOutput s = new ObjectOutputStream(f);

   s.writeObject("test");

   s.writeObject(new ArrayList());

   s.flush();

常见的使用方式是直接将对象写入流中,比如上述例子中,创建了 FileOutputStream 对象,其对应输出到 tmp.o 文件中,然后创建 ObjectOutputStream 对象嵌套前面的输出流。当我们调用 writeObject 方法时即能进行序列化操作。

writeObject 方法这里需要说明下,在对某个对象进行写入时,它其实不仅仅序列化自己,还会去遍历寻找相关引用的其他对象,由自己和其他引用对象组成的一个完整的对象图关系都会被序列化。

对于数组、enum、Class类对象、ObjectStreamClass 和 String 等都会做特殊处理,而其他对象序列化则需要实现 Serializable 或 Externalizable 接口。

反序列化例子

[Java] 纯文本查看 复制代码

?


1

2

3

4

FileInputStream in = new FileInputStream("tmp.o");

    ObjectInputStream s = new ObjectInputStream(in);

    String test = (String)s.readObject();

    List list = (ArrayList)s.readObject();

针对序列化则存在反序列化操作,通过流直接读取对象,先创建 FileInputStream 对象,其对应输入文件为 tmp.o,然后创建 ObjectInputStream 对象嵌套前面的输入流,接着则可以调用 readObject 方法读取对象。

其中调用 readObject 方法反序列操作的过程,除了会恢复对象自己之外还会遍历整个完整的对象图,创建整个对象图包含的所有对象。

serialVersionUID 有什么用
在序列化操作时,经常会看到实现了 Serializable 接口的类会存在一个 serialVersionUID 属性,并且它是一个固定数值的静态变量。比如如下,这个属性有什么作用?其实它主要用于验证版本一致性,每个类都拥有这么一个 ID,在序列化的时候会一起被写入流中,那么在反序列化的时候就被拿出来跟当前类的 serialVersionUID 值进行比较,两者相同则说明版本一致,可以序列化成功,而如果不同则序列化失败。

[Java] 纯文本查看 复制代码

?


1

private static final long serialVersionUID = -6849794470754667710L;

一般情况下我们可以自己定义 serialVersionUID 的值或者 IDE 帮我们自动生成,而如果我们不显示定义 serialVersionUID 的话,这不代表不存在 serialVersionUID,而是由 JDK 帮我们生成,生成规则是会利用类名、类修饰符、接口名、字段、静态初始化信息、构造函数信息、方法名、方法修饰符、方法签名等组成的信息,经过 SHA 算法生成摘要即是最终的 serialVersionUID 值。

父类序列化什么情况

如果一个子类实现了 Serializable 接口而父类没有实现该接口,则在序列化子类时,子类的属性状态会被写入而父类的属性状态将不被写入。所以如果想要父类属性状态也一起参与序列化,就要让它也实现 Serializable 接口。

另外,如果父类未实现 Serializable 接口则反序列化生成的对象会再次调用父类的构造函数,以此完成对父类的初始化。所以父类属性初始值一般都是类型的默认值。比如下面,Father 类的属性不会参与序列化,反序列化时 Father 对象的属性的值为默认值0。

[Java] 纯文本查看 复制代码

?


01

02

03

04

05

06

07

08

09

10

11

12

13

14

public class Father {

        public int f;

        public Father() {

        }

    }

    public class Son extends Father implements Serializable {

        public int s;

        public Son() {

            super();

        }

    }

哪些字段会序列化

在序列化时类的哪些字段会参与到序列化中呢?其实有两种方式决定哪些字段会被序列化,
1. 默认方式,Java对象中的非静态和非transient的字段都会被定义为需要序列的字段。
2. 另外一种方式是通过 ObjectStreamField 数组来声明类需要序列化的对象。

可以看到普通的字段都是默认会被序列化的,而对于某些包含敏感信息的字段我们不希望它参与序列化,那么最简单的方式就是可以将该字段声明为 transient。

如何使用 ObjectStreamField?举个例子,如下,A类中有 name 和 password 两个字段,通过 ObjectStreamField 数组声明只序列化 name 字段。这种声明的方式不用纠结为什么这样,这仅仅是约定了这样而已。

[Java] 纯文本查看 复制代码

?


1

2

3

4

5

6

7

public class A implements Serializable {

       String name;

       String password

       private static final ObjectStreamField[] serialPersistentFields

                    = {new ObjectStreamField("name", String.class)};

    }

枚举类型的序列化

Enum 类型的序列化与普通的 Java 类的序列化有所不同,那么在深入之前可以先看这篇文章深入了解下枚举,《 [从JDK角度认识枚举enum][JDK_enum]》。

所以我们知道枚举被编译后会变成一个继承 java.lang.Enum 的类,而且枚举里面的元素被声明成 static final ,另外生成一个静态代码块 static{},最后还会生成 values 和 valueOf 两个方法。Enum 类是一个抽象类,主要有 name 和 ordinal 两个属性,分别用于表示枚举元素的名称和枚举元素的位置索引。

Enum 类型参与序列化时只会将枚举对象中的 name 属性写入,而其他的属性则不参与进来。在反序列化时,则是先读取 name 属性,然后再通过 java.lang.Enum 类的 valueOf 方法找到对应的枚举类型。

除此之外,不能自定义 Enum 类型的序列化,所以 writeObject, readObject, readObjectNoData, writeReplace 以及 readResolve 等方法在序列化时会被忽略,类似的,serialPersistentFields 和 serialVersionUID 属性都会被忽略。

最后,在序列化场景中,涉及到使用枚举的情况时要仔细设计好,不然很可能会因为后面升级修改了枚举类的结构而导致反序列化失败。

Externalizable 接口作用

Externalizable 接口主要就是提供给用户自己控制序列化内容,虽然前面我们也看到了 transient 和 ObjectStreamField 能定义序列化的字段,但通过 Externalizable 接口则能更加灵活。可以看到它其实继承了 Serializable 接口,提供了 writeExternal 和 readExternal 两个方法,也就是在这两个方法内控制序列化和反序列化的内容。

[Java] 纯文本查看 复制代码

?


1

2

3

4

5

6

public interface Externalizable extends java.io.Serializable {

        void writeExternal(ObjectOutput out) throws IOException;

        void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

    }

比如下面的例子,我们可以在 writeExternal 方法中额外写入 Date 对象,然后再写入 value 值。对应的,反序列化时则是在 readExternal 方法中读取 Date 对象和 value。这样就完成了自定义序列化操作。

[Java] 纯文本查看 复制代码

?


01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

public class ExternalizableTest implements Externalizable {

        public String value = "test";

        public ExternalizableTest() {

        }

        public void writeExternal(ObjectOutput out) throws IOException {

            Date d = new Date();

            out.writeObject(d);

            out.writeObject(value);

        }

        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

            Date d = (Date) in.readObject();

            System.out.println(d);

            System.out.println((String) in.readObject());

        }

    }

写入时替换对象

正常情况下序列化某个对象时写入的正是当前的对象,但如果说我们要替换当前的对象而写入其他对象的话则可以通过 writeReplace 方法来实现。比如下面,person 类通过 writeReplace 方法最终可以写入 Object 数组对象。所以我们在反序列化时就不再是转换成 Person 类型,而是要转换为 Object 数组对象。

[Java] 纯文本查看 复制代码

?


01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

class Person implements Serializable {

        private String name;

        private int age;

        public Person(String name, int age) {

            this.name = name;

            this.age = age;

        }

        private Object writeReplace() throws ObjectStreamException {

            Object[] properties = new Object[2];

            properties[0] = name;

            properties[1] = age;

            return properties;

        }

    }

[Java] 纯文本查看 复制代码

?


1

2

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.o"));

   Object[] properties = (Object[]) ois.readObject();

读取时替换对象

上面介绍了在写入时可以替换对象,而在读取时也同样支持替换对象的,它是通过 readResolve 方法实现的。比如下面,在 readResolve 方法返回 2222,则反序列化读取时不再是 Person 对象,而是 2222。

[Java] 纯文本查看 复制代码

?


01

02

03

04

05

06

07

08

09

10

11

12

13

class Person implements Serializable {

        private String name;

        private int age;

        public Person(String name, int age) {

            this.name = name;

            this.age = age;

        }

        private Object readResolve() throws ObjectStreamException {

            return 2222;

        }

    }

[Java] 纯文本查看 复制代码

?


1

2

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.o"));

    Object o = ois.readObject();

更多技术资讯可关注:itheimaGZ获取

原文地址:https://www.cnblogs.com/zhuxiaopijingjing/p/12268123.html

时间: 2024-10-09 03:04:59

细看Java序列化机制的相关文章

Java序列化机制剖析

Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的过程.Java序列化API提供一种处理对象序列化的标准机制.在这里你能学到如何序列化一个对象,什么时候需要序列化以及Java序列化的算法,我们用一个实例来示范序列化以后的字节是如何描述一个对象的信息的. 序列化的必要性 Java中,一切都是对象,在分布式环境中经常需要将Object从这一端网络或设备传递到另一端.这就需要有一种可以

Java序列化机制和原理

Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的过程.Java序列化API提供一种处理对象序列化的标准机制.在这里你能学到如何序列化一个对象,什么时候需要序列化以及Java序列化的算法,我们用一个实例来示范序列化以后的字节是如何描述一个对象的信息的. 序列化的必要性 Java中,一切都是对象,在分布式环境中经常需要将Object从这一端网络或设备传递到另一端. 这就需要有一种可

输入和输出--java序列化机制

对象的序列化 什么是Java对象的序列化? 对象序列化的目标是将对象保存到磁盘上,或允许在网络中直接传输对象.对象序列化机制允许把内存中的Java对象转换成与平台无关的二进制流,从而保存或者传输.其他的程序一旦获得这种二进制流,还可以把这种二进制流恢复成原来的Java对象 序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础.因此序列化机制是 JavaEE 平台的基础.要养成良好的习惯,在写一个ja

java 序列化机制深度解析

概要 序列化机制允许将实现序列化的Java对象转换为字节序列,这些字节序列可以被保存在磁盘上或通过网络传输,以备以后重新恢复原来的对象,序列化机制使得对象可以脱离程序的运行而独立存在 可序列化的类包括:实现了Serializable的类,数组,枚举,String类也是可序列化对象 由于序列化保存的是对象的状态,因此不会保存类的静态变量 -通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一

Java序列化机制原理

Java序列化就是将一个对象转化为一个二进制表示的字节数组,通过保存或则转移这些二进制数组达到持久化的目的.要实现序列化,需要实现java.io.Serializable接口.反序列化是和序列化相反的过程,就是把二进制数组转化为对象的过程.在反序列化的时候,必须有原始类的模板才能将对象还原.从这个过程我们可以猜测到,序列化过程并不想class文件那样保存类的完整的结构信息.下面我们以一个简单的例子来看一下,序列化的时候都保存了哪些信息.代码如下: package com.ysl; import

Java序列化与反序列化学习(三):序列化机制与原理

Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的 过程.Java序列化API提供一种处理对象序列化的标准机制.在这里你能学到如何序列化一个对象,什么时候需要序列化以及Java序列化的算法,我们用 一个实例来示范序列化以后的字节是如何描述一个对象的信息的. 序列化的必要性 Java中,一切都是对象,在分布式环境中经常需要将Object从这一端网络或设备传递到另一端.这就需要有一种

java序列化的机制与原理

有关Java对象的序列化和反序列化也算是Java基础的一部分,下面对Java序列化的机制和原理进行一些介绍. Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的过程.Java序列化API提供一种处理对象序列化的标准机制.在这里你能学到如何序列化一个对象,什么时候需要序列化以及Java序列化的算法,我们用一个实例来示范序列化以后的字节是如何描述一个对象的信息的. 序列化的必要性 J

Java 和 Hadoop 序列化机制浅讲

1.序列化 序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程(字节流).在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象. 通常来说有三个用途: 持久化:对象可以被存储到磁盘上 通信:对象可以通过网络进行传输 拷贝.克隆:可以通过将某一对象序列化到内存的缓冲区,然后通过反序列化生成该对象的一个深拷贝(破解单例模式的一种方法) 2.Java序列化机制 在Java中要实现序列化,只需要实现S

Java 序列化深入分析

序列化机制介绍 ??序列化是指把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中.序列化后的字节流保存了Java对 象的状态以及相关的描述信息.客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象.本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态.序列化机制的核心作用就是对象状态的 保存与重建. Java序列化机制解析 ??Java API提供了对序列化的支持,要实