对象序列化的目的

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。

  1. public class Person implements Serializable {
  2.  
  3. private static final long serialVersionUID = 7686111088673836789L;
  4.  
  5. private int id;
  6.  
  7. private String name;
  8.  
  9. private static String company;
  10.  
  11. private transient int age;
  12.  
  13. static {
  14. company = "Apple";
  15. }
  16.  
  17. public Person(int id, String name, int age) {
  18. super();
  19. this.id = id;
  20. this.name = name;
  21. this.age = age;
  22. }
  23.  
  24. @Override
  25. public String toString() {
  26. return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
  27. }
  28.  
  29. }

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

  1. public class SerializableTest {
  2.  
  3. /** 将对象序列化至文件 */
  4. public void serialize(Serializable object, String filePath)
  5. throws IOException {
  6. File file = null;
  7. ObjectOutputStream out = null;
  8.  
  9. try {
  10. file = new File(filePath);
  11. out = new ObjectOutputStream(new FileOutputStream(file));
  12. out.writeObject(object);
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. } finally {
  16. if (out != null)
  17. out.close();
  18. }
  19. }
  20.  
  21. /** 将对象从文件中反序列化出来 */
  22. public Object deserialize(String filePath) throws IOException {
  23. File file = null;
  24. ObjectInputStream in = null;
  25.  
  26. try {
  27. file = new File(filePath);
  28. in = new ObjectInputStream(new FileInputStream(file));
  29. return in.readObject();
  30. } catch (Exception e) {
  31. e.printStackTrace();
  32. } finally {
  33. if (in != null)
  34. in.close();
  35. }
  36. return null;
  37. }
  38.  
  39. public static void main(String[] args) throws IOException {
  40. SerializableTest test = new SerializableTest();
  41.  
  42. String filePath = "output/person.ser";
  43. test.serialize(new Person(24, "kobe", 37), filePath);
  44. Person person = (Person) test.deserialize(filePath);
  45. System.out.println("person = " + person);
  46. }
  47. }

在此,我们生成了一个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"异常信息,请各位大牛不惜赐教)。例如:

  1. public class Person implements Serializable {
  2.  
  3. private static final long serialVersionUID = -4481514608698081671L;
  4.  
  5. private transient Integer id;
  6.  
  7. private String name;
  8.  
  9. private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("id", Integer.class) };
  10. }

通过这种方法,具有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接口的类其实都有几个隐藏的方法可重写,进而控制整个序列化与反序列化的过程,如:

  1. private void writeObject(ObjectOutputStream out) throws IOException;
  2.  
  3. private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException;
  4.  
  5. Object writeReplace() throws ObjectStreamException;
  6.  
  7. 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. gradle学习笔记(1)

    1. 安装     (1) 下载最新gradle压缩包,解压到某处.地址是:Gradle web site:     (2) 添加环境变量:             1) 变量名:GRADLE_HOM ...

  2. RestTemplate发送请求并携带header信息

    1.使用restTemplate的postForObject方法 注:目前没有发现发送携带header信息的getForObject方法. HttpHeaders headers = new Http ...

  3. 零OCR基础6行代码实现C#验证码识别

    这两天因为工作需要,要到某个网站采集信息,一是要模拟登陆,二是要破解验证码,本想用第三方付费打码,但是想想网上免费的代码也挺多的,于是乎准备从网上撸点代码下来,谁知道,撸了好多个都不行,本人以前也没接 ...

  4. 如何利用pt-online-schema-change进行MySQL表的主键变更

    业务运行一段时间,发现原来的主键设置并不合理,这个时候,想变更主键.这种需求在实际生产中还是蛮多的. 下面,看看pt-online-schema-change解决这类问题的处理方式. 首先,创建一张测 ...

  5. git-2.10.2-64-bit介绍&&git下载&&git安装教程

    Git介绍 分布式:Git系统是一个分布式的系统,是用来保存工程源代码历史状态的命令行工具. 保存点:Git的保存点可以追踪源码中的文件, 并能得到某一个时间点上的整个工程项目的状态:可以在该保存点将 ...

  6. JDBC增加删除修改

    一.配置程序--让我们程序能找到数据库的驱动jar包 1.把.jar文件复制到项目中去,整合的时候方便. 2.在eclipse项目右击"构建路径"--"配置构建路径&qu ...

  7. 编译器开发系列--Ocelot语言2.变量引用的消解

    "变量引用的消解"是指确定具体指向哪个变量.例如变量"i"可能是全局变量i,也可能是静态变量i,还可能是局部变量i.通过这个过程来消除这样的不确定性,确定所引用 ...

  8. java.lang.NoSuchFieldError: org.apache.http.message.BasicLineFormatter.INSTANCE

    Android发出HTTP请求时出现了这个错误: java.lang.NoSuchFieldError: org.apache.http.message.BasicLineFormatter.INST ...

  9. [转载]敏捷开发之Scrum扫盲篇

    现在敏捷开发是越来越火了,人人都在谈敏捷,人人都在学习Scrum和XP...      为了不落后他人,于是我也开始学习Scrum,今天主要是对我最近阅读的相关资料,根据自己的理解,用自己的话来讲述S ...

  10. emmet,jade,haml, slim,less,sass,coffeescript等的实战优缺点

    摘要: 文章背景,来自于群内周五晚上的一次头脑风暴式的思维碰撞交流活动. 随着前端技术的蓬勃发展, 各种新技术随着生产力的需要不断的涌入我们的视野, 那今天探讨的话题是这些新时代的前端兵器谱: 一. ...