对象序列化的目的

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. favicon.ioc使用以及注意事项

    1.效果 2.使用引入方法 2.1 注意事项:(把图标命名为favicon.ico,并且放在根目录下,同时使用Link标签,多重保险) 浏览器默认使用根目录下的favicon.ico 图标(如果你并没 ...

  2. CoreCRM 开发实录——开始之新项目的技术选择

    2016年11月,接受了一个工作,是对"悟空CRM"进行一些修补.这是一个不错的 CRM,开源,并提供一个 SaaS 的服务.正好微软的 .NET Core 和 ASP.NET C ...

  3. 运用php做投票题,例题

    要求大概是这样的,有一个题目,题目下面是复选框,要求点完复选框提交后会变成进度条,各选项的进度条百分比,和投票数量 首先还是要在数据库建两张表,如下: 要完成这个题目,需要建两个页面 <!DOC ...

  4. Android业务组件化之子模块SubModule的拆分以及它们之间的路由Router实现

    前言: 前面分析了APP的现状以及业务组件化的一些探讨(Android业务组件化之现状分析与探讨),以及通信的桥梁Scheme的使用(Android业务组件化之URL Scheme使用),今天重点来聊 ...

  5. QT5利用chromium内核与HTML页面交互

    在QT5.4之前,做QT开发浏览器只能选择QWebkit,但是有过使用的都会发现,这个webkit不是出奇的慢,简直是慢的令人发指,Release模式下还行,debug下你就无语了,但是webkit毕 ...

  6. spring注解源码分析--how does autowired works?

    1. 背景 注解可以减少代码的开发量,spring提供了丰富的注解功能.我们可能会被问到,spring的注解到底是什么触发的呢?今天以spring最常使用的一个注解autowired来跟踪代码,进行d ...

  7. [C#] C# 知识回顾 - 特性 Attribute

    C# 知识回顾 - 特性 Attribute [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5911289.html 目录 特性简介 使用特性 特性 ...

  8. JS图片上传预览插件制作(兼容到IE6)

    其实,图片预览功能非常地常见.很意外,之前遇到上传图片的时候都不需要预览,也一直没有去实现过.现在手上的项目又需要有图片预览功能,所以就动手做了一个小插件.在此分享一下思路. 一.实现图片预览的一些方 ...

  9. EC笔记:第4部分:21、必须返回对象时,别返回引用

    使用应用可以大幅减少构造函数与析构函数的调用次数,但是引用不可以滥用. 如下: struct St { int a; }; St &func(){ St t; return t; } 在返回t ...

  10. VPN连接常见错误汇总

    提示远程服务器没有响应. 这种情况有两种情况,一种是远程服务器出现故障.另一种是自己的电脑出现问题,具体原因我还没有找到,但是可以肯定的是注册表除了问题,一个终极的解决办法就是把注册表替换了.先将HK ...