对象序列化的目的

1)希望将Java对象持久化在文件中

2)将Java对象用于网络传输


实现方式

如果希望一个类的对象可以被序列化/反序列化,那该类必须实现java.io.Serializable接口或java.io.Externalizable接口,前者为一个标记接口,即不存在任何需要实现的方法,仅仅为一种对象可序列化的标识,且序列化和反序列化的方式为系统默认方式;而后者其实内部也实现了Serializable,并且包含两个方法writeExternal()和readExternal()分别用于对象的写入与读取,因此可以采用自定义的方式进行序列化/反序列化,比Serializable更灵活。

而在编写序列化类时还有一个重要的属性serialVersionUID,该属性为long型,且必须为static和final,用于校验序列化时的对象与反序列化出的对象是否为同一个类对应的对象。该值的生成不建议使用系统动态提供的默认值,而应使用IDE自带的UID生成功能。


代码示例

写一个简单的类Person,实现了Serializable接口。注意到该类有四个属性,其中company为static,age为transient。

public class Person implements Serializable {

    private static final long serialVersionUID = 7686111088673836789L;

    private int id;

    private String name;

    private static String company;

    private transient int age;

    static {
company = "Apple";
} public Person(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
} @Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
} }

一旦一个类可序列化之后,我们就可以通过ObjectOutputStream / ObjectInputStream将该类的某个对象序列化存储在文件中,并随后从文件中反序列出来还原该对象。

public class SerializableTest {

    /** 将对象序列化至文件 */
public void serialize(Serializable object, String filePath)
throws IOException {
File file = null;
ObjectOutputStream out = null; try {
file = new File(filePath);
out = new ObjectOutputStream(new FileOutputStream(file));
out.writeObject(object);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null)
out.close();
}
} /** 将对象从文件中反序列化出来 */
public Object deserialize(String filePath) throws IOException {
File file = null;
ObjectInputStream in = null; try {
file = new File(filePath);
in = new ObjectInputStream(new FileInputStream(file));
return in.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null)
in.close();
}
return null;
} public static void main(String[] args) throws IOException {
SerializableTest test = new SerializableTest(); String filePath = "output/person.ser";
test.serialize(new Person(24, "kobe", 37), filePath);
Person person = (Person) test.deserialize(filePath);
System.out.println("person = " + person);
}
}

在此,我们生成了一个Person对象,并传入了三个参数,随后将其存储到person.ser文件中,并接着从该文件中读取反序列化出该对象。最后的输出为:

person = Person [id=24, name=kobe, age=0]

id和name两个值没有问题,可为何age=0,而不是传入的37呢?这就是transient这个关键字的作用了,原来并不是所有的属性都会参与序列化过程,比如用static和transient关键字标识的属性。因为static属性意味着是该类的全部对象所共同拥有的,并不属于某个具体的对象,因此不应该在对象序列化的时候出现;而transient关键词的意义则是为了在某些特定属性(如某些敏感的密码信息等)不希望其存储在文件中时人为添加上去的。当然,其实有另一种反向确定可序列化属性的方法,即定义一个类型为ObjectStreamField[]、名称为serialPersistentFields的特殊属性,并且它必须是private、static和final的,由serialPersistentFields就可以确定哪些属性是希望参与序列化的,甚至包括默认不可序列化的static和transient属性(static属性本人并未测试成功,总是抛出"unmatched serializable field(s) declared"异常信息,请各位大牛不惜赐教)。例如:

public class Person implements Serializable {

    private static final long serialVersionUID = -4481514608698081671L;

    private transient Integer id;

    private String name;

    private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("id", Integer.class) };
}

通过这种方法,具有transient特性的id会被序列化,而原本默认会被序列化的name却不会被序列化,因为serialPersistentFields中仅指定了id为可序列化属性。


序列化流协议存储格式

将之前那个Person对象序列化至文件后,可用Binary Viewer二进制查看工具(Windows平台下)打开,让我们一起了解一下序列化文件的存储格式,内容如下图所示(注意只有id和name属性可序列化,company和age并不会)。

这些十六进制的数字代表的含义遵循Java对象序列化流协议,下面依次介绍它们的含义。

AC ED:STREAM_MAGIC
00 05:STREAM_VERSION
73:TC_OBJECT
72:TC_CLASSDESC
00 06:类名的长度,即Person的长度,为6
50 65 72 73 6F 6E:类名,即Person
6A AA 10 8D 4F F8 98 4E F5:serialVersionUID的值,为7686111088673836789
02:classDescFlags,在此为SC_SERIALIZABLE,因为该类实现了Serializable接口
00 02:属性个数,在此可序列化的属性个数为2
49:I,表示int,因为id是int类型
00 02:属性名称长度,即id的长度,为2
69 64:属性名称,即id
4C:L,表示类类型,因为String是类类型,区别于基本类型
00 04:属性名称的长度,即name的长度,为4
6E 61 6D 65:属性名称,即name
74:TC_STRING,表示字符串
00 12:类型名称长度,即下面的"Ljava/lang/String;"的长度,为18
4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B:Ljava/lang/String;其中L表示类类型(注意结尾有个;分号)
78:TC_ENDBLOCKDATA
70:TC_NULL
00 00 00 18:属性值,即id的值,为24
74:TC_STRING,表示字符串
00 04:属性值的长度,即name的值kobe的长度,为4
6B 6F 62 65:属性值,即name的值,为kobe


自定义序列化/反序列化过程

凡是实现了Serializable接口的类其实都有几个隐藏的方法可重写,进而控制整个序列化与反序列化的过程,如:

private void writeObject(ObjectOutputStream out) throws IOException;

private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException;

Object writeReplace() throws ObjectStreamException;

Object readResolve() throws ObjectStreamException;

其中writeReplace()和readResolve()方法为钩子函数,分别在writeObject()调用之前和readObject()调用之后被调用。其中readResolve()方法在本人另一篇关于Singleton的博文中出现过,目的就是防止不怀好意之人通过对象序列化技术人为破坏单例模式,构造出新的对象,因为通过readObject()方法可以直接构造新对象而避开私有构造方法。但正因为readResolve()方法在readObject()反序列化对象后被调用,因此可用来替换读取出的流对象。


References

[1] https://docs.oracle.com/javase/8/docs/platform/serialization/spec/protocol.html#a8299

[2] https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html#a6250

[3] http://www.javaworld.com/article/2072752/the-java-serialization-algorithm-revealed.html

[4] https://www.javacodegeeks.com/2015/09/built-in-serialization-techniques.html

[5] http://steven2011.iteye.com/blog/1299499

[6] http://stackoverflow.com/questions/1168348/java-serialization-readobject-vs-readresolve


为尊重原创成果,如需转载烦请注明本文出处:http://www.cnblogs.com/fernandolee24/p/5682738.html,特此感谢

Java对象序列化剖析的更多相关文章

  1. 理解Java对象序列化

    http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html 1. 什么是Java对象序列化 Java平台允许我们在内存中创 ...

  2. java 对象序列化与反序列化

    Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化  Java序列化是指把Java对象转换为 ...

  3. java 对象序列化

    java 对象序列化 package org.rui.io.serializable; import java.io.ByteArrayInputStream; import java.io.Byte ...

  4. 理解Java对象序列化(二)

    关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制.在撰写本文时,既参考了Th ...

  5. 关于 Java 对象序列化您不知道的 5 件事

    数年前,当和一个软件团队一起用 Java 语言编写一个应用程序时,我体会到比一般程序员多知道一点关于 Java 对象序列化的知识所带来的好处. 关于本系列 您觉得自己懂 Java 编程?事实上,大多数 ...

  6. java 对象序列化 RMI

    对于一个存在于Java虚拟机中的对象来说,其内部的状态只保持在内存中.JVM停止之后,这些状态就丢失了.在很多情况下,对象的内部状态是需要被持久化下来的.提到持久化,最直接的做法是保存到文件系统或是数 ...

  7. Java对象序列化入门

      Java对象序列化入门 关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制 ...

  8. Java对象序列化与反序列化一 JSON

    Java对象序列化与反序列化一 JSON 1. 依赖库 jackson-all-1.6.1.jar 2. 代码 public class Student {    private String nam ...

  9. Java对象序列化/反序列化的注意事项(转)

    Java对象序列化 对于一个存在Java虚拟机中的对象来说,其内部的状态只是保存在内存中.JVM退出之后,内存资源也就被释放,Java对象的内部状态也就丢失了.而在很多情况下,对象内部状态是需要被持久 ...

随机推荐

  1. web前端基础知识

    #HTML    什么是HTML,和他ML...    网页可以比作一个装修好了的,可以娶媳妇的房子.    房子分为:毛坯房,精装修    毛坯房的修建: 砖,瓦,水泥,石头,石子....    精 ...

  2. JavaScript常见的五种数组去重的方式

    ▓▓▓▓▓▓ 大致介绍 JavaScript的数组去重问题在许多面试中都会遇到,现在做个总结 先来建立一个数组 var arr = [1,2,3,3,2,'我','我',34,'我的',NaN,NaN ...

  3. javascript工厂模式和构造函数模式创建对象

    一.工厂模式 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程(本书后面还将讨论其他设计模式及其在JavaScript 中的实现).考虑到在ECMAScript 中无法创 ...

  4. 深入浅出JavaScript之原型链&继承

    Javascript语言的继承机制,它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instanc ...

  5. python 数据类型---文件二

    1.打印进度条 import sys,time for i in range(20): sys.stdout.write("#") sys.stdout.flush() #不等缓冲 ...

  6. Maven 整合FreeMarker使用

    pom.xml <!-- freemarker jar --> <dependency> <groupId>org.freemarker</groupId&g ...

  7. java8中lambda表达式的应用,以及一些泛型相关

    语法部分就不写了,我们直接抛出一个实际问题,看看java8的这些新特性究竟能给我们带来哪些便利 顺带用到一些泛型编程,一切都是为了简化代码 场景: 一个数据类,用于记录职工信息 public clas ...

  8. github免输用户名/密码SSH登录的配置

    从github上获取的,自己整理了下,以备后用. Generating an SSH key mac windows SSH keys are a way to identify trusted co ...

  9. Python处理Excel表格

    同事小王今天说他有一个Excel表格,表格如下,一列是姓名,一列是电话号码,总共有大概2000行数据. 有的姓名占了一行,有的占了两行,还有一些占了三行的.如下图: 他问我可不可以全部统一成一行,而且 ...

  10. 解除win7网络限速.

    在电脑刚买或者系统重装了的时候,win7系统会默认限制20%的网络速度,限制了我们的上网速度,我们可以解决这个限制,让上网变得更快 下面是操作步骤 1.开始>运行 2.输入以下命令,然后确定 g ...