Java对象序列化剖析
对象序列化的目的
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对象序列化剖析的更多相关文章
- 理解Java对象序列化
http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html 1. 什么是Java对象序列化 Java平台允许我们在内存中创 ...
- java 对象序列化与反序列化
Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为 ...
- java 对象序列化
java 对象序列化 package org.rui.io.serializable; import java.io.ByteArrayInputStream; import java.io.Byte ...
- 理解Java对象序列化(二)
关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制.在撰写本文时,既参考了Th ...
- 关于 Java 对象序列化您不知道的 5 件事
数年前,当和一个软件团队一起用 Java 语言编写一个应用程序时,我体会到比一般程序员多知道一点关于 Java 对象序列化的知识所带来的好处. 关于本系列 您觉得自己懂 Java 编程?事实上,大多数 ...
- java 对象序列化 RMI
对于一个存在于Java虚拟机中的对象来说,其内部的状态只保持在内存中.JVM停止之后,这些状态就丢失了.在很多情况下,对象的内部状态是需要被持久化下来的.提到持久化,最直接的做法是保存到文件系统或是数 ...
- Java对象序列化入门
Java对象序列化入门 关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制 ...
- Java对象序列化与反序列化一 JSON
Java对象序列化与反序列化一 JSON 1. 依赖库 jackson-all-1.6.1.jar 2. 代码 public class Student { private String nam ...
- Java对象序列化/反序列化的注意事项(转)
Java对象序列化 对于一个存在Java虚拟机中的对象来说,其内部的状态只是保存在内存中.JVM退出之后,内存资源也就被释放,Java对象的内部状态也就丢失了.而在很多情况下,对象内部状态是需要被持久 ...
随机推荐
- 05.LoT.UI 前后台通用框架分解系列之——漂亮的时间选择器
LOT.UI分解系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下:https://github.com/du ...
- 使用AWS亚马逊云搭建Gmail转发服务(三)
title: 使用AWS亚马逊云搭建Gmail转发服务(三) author:青南 date: 2015-01-02 15:42:22 categories: [Python] tags: [log,G ...
- 【原】实时渲染中常用的几种Rendering Path
[原]实时渲染中常用的几种Rendering Path 本文转载请注明出处 —— polobymulberry-博客园 本文为我的图形学大作业的论文部分,介绍了一些Rendering Path,比较简 ...
- 使用Java原生代理实现AOP
### 本文由博主柒.原创,转载请注明出处 ### 完整源码下载地址 [https://github.com/MatrixSeven/JavaAOP](https://github.com/Matri ...
- jQuery学习之路(7)- 用原生JavaScript实现jQuery的某些简单功能
▓▓▓▓▓▓ 大致介绍 学习了妙味,用原生的JavaScript实现jQuery中的某些部分功能 定义自己的函数库lQuery ▓▓▓▓▓▓ $()选择器的实现 jQuery是面向对象的,所以自己编写 ...
- Mysql命令大全
格式: mysql -h主机地址 -u用户名 -p用户密码 1.连接到本机上的MYSQL.首先打开DOS窗口,然后进入目录mysql\bin,再键入命令mysql -u root -p,回车后提示你输 ...
- 最好的.NET开源免费ZIP库DotNetZip(.NET组件介绍之三)
在项目开发中,除了对数据的展示更多的就是对文件的相关操作,例如文件的创建和删除,以及文件的压缩和解压.文件压缩的好处有很多,主要就是在文件传输的方面,文件压缩的好处就不需要赘述,因为无论是开发者,还是 ...
- ActiveRecord模式整理
DAO Data Access Object,数据访问对象 DAO是一个软件设计的指导原则,在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应用,应该将所有对数据源的访问操作抽象 ...
- 升级npm
查看npm的所有版本 运行命令: npm view npm versions 命令运行后,会输出到目前为止npm的所有版本. [ '1.1.25', '1.1.70', '1.1.71', '1.2. ...
- 嵌入式&iOS:回调函数(C)与block(OC)回调对比
学了OC的block,再写C的回调函数有点别扭,对比下区别,回忆记录下. C的回调函数: callBack.h 1).定义一个回调函数的参数数量.类型. typedef void (*CallBack ...