下面的文章在公众号作了更新:点击查看最新文章
可识别二维码查看更多最新文章:

写在前面


Java对象是在JVM中生成的,如果需要远程传输或保存到硬盘上,就需要将Java对象转换成可传输的文件流。
市面上目前有的几种转换方式:

  • 1. 利用Java的序列化功能序列成字节(字节流)也就是接下来要讲的。一般是需要加密传输时才用。
  • 2. 将对象包装成JSON字符串(字符流)
    转Json工具有Jackson、FastJson或者GJson,它们各有优缺点:

    • JackSon:Map、List的转换可能会出现问题。转复杂类型的Bean时,转换的Json格式不是标准的Json格式。适合处理 大文本Json。
    • FastJosn:速度最快。将复杂类型的Bean转换成Json可能会有问题:引用类型如果没有引用被出错。适合对性能有要求的场景。
    • GJson:功能最全,可以将复杂的Bean和Json字符串进行互转。性能上面比FastJson有所差距。适合处理小文本Json,和对于数据正确性有要求的场景。
  • 3. protoBuf工具(二进制)
    性能好,效率高,字节数很小,网络传输节省IO。但二进制格式可读性差。

一、定义


序列化:把Java对象转换为字节序列的过程。
  
  反序列化:把字节序列恢复为Java对象的过程。

二、用途


  • 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;(持久化对象
  • 在网络上传送对象的字节序列。(网络传输对象
      
      Java平台允许我们在内存中创建可复用的Java对象,但只有当JVM(Java虚拟机)处于运行时,这些对象才可能存在,也就是这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存指定的对象(持久化对象),并在将来重新读取被保存的对象。
        
      网络通信时,无论是何种类型的数据,都会转成字节序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

三、实现


实现了如下两个接口之一的类的对象才能被序列化:
  
  1) Serializable
  
  2) Externalizable

序列化:ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

反序化:ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

注:使用writeObject() 和readObject()方法的对象必须已经被序列化

四、serialVersionUID


如果serialVersionUID没有显式生成,系统就会自动生成一个。此时,如果在序列化后我们将该类作添加或减少一个字段等的操作,系统在反序列化时会重新生成一个serialVersionUID然后去和已经序列化的对象进行比较,就会报序列号版本不一致的错误。为了避免这种问题, 一般系统都会要求实现serialiable接口的类显式的生明一个serialVersionUID。

所以显式定义serialVersionUID有如下两种用途:
   1、 希望类的不同版本对序列化兼容时,需要确保类的不同版本具有相同的serialVersionUID;
   2、 不希望类的不同版本对序列化兼容时,需要确保类的不同版本具有不同的serialVersionUID。

五、序列化机制算法


1. 所有保存到磁盘中的对象都有一个序列化编号
  
  2. 当程序试图序列化一个对象时,程序先检查该对象是否已经被序列化过。如果从未被序列化过,系统就会将该对象转换成字节序列并输出;如果已经序列化过,将直接输出一个序列化编号。

六、示例


要被序列化的对象对应的类的代码:

public class Person implements Serializable {  private String name = null;  private Integer age = null;   public Person(){System.out.println("无参构造");}public Person(String name, Integer age) {  this.name = name;  this.age = age;  }  //getter setter方法省略...@Override public String toString() {  return "[" + name + ", " + age+"]";  }
}

MySerilizable 是一个简单的序列化程序,它先将一个Person对象保存到文件person.txt中,然后再从该文件中读出被存储的Person对象,并打印该对象。

public class MySerilizable {public static void main(String[] args) throws Exception {  File file = new File("person.txt");  //序列化持久化对象ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));  Person person = new Person("Peter", 27);  out.writeObject(person);  out.close();  //反序列化,并得到对象ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));  Object newPerson = in.readObject(); // 没有强制转换到Person类型  in.close();  System.out.println(newPerson);  }
}

输出结果:

[Peter, 27]

结果没有打印“无参构造”,说明反序列化机制无需通过构造器来初始Java对象。
  注:
  
  1.) 反序列化读取的仅仅是Java对象的数据,而不是Java类,所以在反序列化时必须提供该Java对象所属类的class文件(这里是Person.class),否则会引发ClassNotFoundException异常。
  
  2).当重新读取被保存的Person对象时,并没有调用Person的任何构造器,说明反序列化机制无须通过构造器来初始化对象。

七、选择序列化


transient

当对某个对象进行序列化时,系统会自动将该对象的所有属性依次进行序列化,如果某个属性引用到别一个对象,则被引用的对象也会被序列化。如果被引用的对象的属性也引用了其他对象,则被引用的对象也会被序列化。 这就是递归序列化。

有时候,我们并不希望出现递归序列化,或是某个存敏感信息(如银行密码)的属性不被序列化,我们就可通过transient关键字修饰该属性来阻止被序列化。

将上面的Person类的age属性用transient修饰:

transient private Integer age = null;

再去执行MySerilizable的结果为:

[Peter, null] //返序列化时没有值,说明age字段未被序列化

writeObject()方法与readObject()方法

使用transient关键字阻止序列化虽然简单方便,但被它修饰的属性被完全隔离在序列化机制之外,导致了在反序列化时无法获取该属性的值,而通过在需要序列化的对象的Java类里加入writeObject()方法与readObject()方法可以控制如何序列化各属性,甚至完全不序列化某些属性(此时就transient一样)。
  
  如果我们想要上面的Person类里的name属性在序列化后存在文件里不让别人知道具体是什么(加密),我们就可在Person类里加如下代码:

//自定义序列化private void writeObject(ObjectOutputStream out) throws IOException {        // out.defaultWriteObject();  // 将当前类的非静态和非瞬态字段写入此流。//如果不写,如果还有其他字段,则不会被序列化out.writeObject(new StringBuffer(name).reverse());//将name简单加密(反转),这样别人就知道是怎么回事,当然实际应用不可能这样加密。out.writeInt(age);  }  //反序列化private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {  //in.defaultReadObject();// 从此流读取当前类的非静态和非瞬态字段。//如果不写,其他字段就不能被反序列化name = ((StringBuffer)in.readObject()).reverse().toString();  //解密age = in.readInt();  }

详细的自定义序列化与反序列化可参见ObjectOutputStream 和ObjectInputStream 类的JDK文档。

Externalizable接口

Externalizable接口 与Serializable 接口类似,只是Externalizable接口需要强制自定义序列化。
要序列化对象的代码:

public class Teacher implements Externalizable{private String name;private Integer age;public Teacher(){System.out.println("无参构造");}public Teacher(String name,Integer age){System.out.println("有参构造");this.name = name;this.age = age;}//setter、getter方法省略@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(new StringBuffer(name).reverse()); //将name简单加密//out.writeInt(age);  //注掉这句后,age属性将不能被序化}@Overridepublic void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {name = ((StringBuffer) in.readObject()).reverse().toString();//age = in.readInt();  }@Override public String toString() {  return "[" + name + ", " + age+ "]";  } }

主函数代码改为:

public class MySerilizable {public static void main(String[] args) throws Exception {  File file = new File("person.txt");  //序列化持久化对象ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));  Teacher person = new Teacher("Peter", 27);  out.writeObject(person);  out.close();  //反序列化,并得到对象ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));  Object newPerson = in.readObject(); // 没有强制转换到Person类型  in.close();  System.out.println(newPerson);  }
}

打印结果:

有参构造
无参构造 //与Serializable 不同的是,还调用了无参构造
[Peter, null] //age未被序列化,所以未取到值

八、单例模式的序列化


当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能略有不同。对前面使用的Person类进行修改,使其实现Singleton模式,如下所示:

public class Person implements Serializable {  private static class InstanceHolder {  private static final Person instatnce = new Person("John", 31, "男");  }  public static Person getInstance() {  return InstanceHolder.instatnce;  }  private String name = null;  private Integer age = null;  private String gender = null;  private Person() {  System.out.println("必须私有化的无参构造");  }  private Person(String name, Integer age, String gender) {  System.out.println("有参构造");  this.name = name;  this.age = age;  this.gender = gender;  }  ...
}

同时要修改MySerilizable 应用,使得能够保存/获取上述单例对象,并进行对象相等性比较,如下代码所示:

public class MySerilizable {  public static void main(String[] args) throws Exception {  File file = new File("person.txt");  ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));  out.writeObject(Person.getInstance()); // 保存单例对象  out.close();  ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));  Object newPerson = in.readObject();  in.close();  System.out.println(newPerson);  System.out.println(Person.getInstance() == newPerson); // 将获取的对象与Person类中的单例对象进行相等性比较  }
}

打印结果:

有参构造
[John, 31, 男]
false  //说明不是同一个对象

九、序列化对象注意事项


  1. 对象的类名、属性都会被序列化;而方法、static属性(静态属性)、transient属性(即瞬态属性)都不会被序列化(这也就是第4条注意事项的原因)
  2. 虽然加static也能让某个属性不被序列化,但static不是这么用的
  3. 要序列化的对象的引用属性也必须是可序列化的,否则该对象不可序列化,除非以transient关键字修饰该属性使其不用序列化。
  4. 反序列化地象时必须有序列化对象生成的class文件(很多没有被序列化的数据需要从class文件获取)
  5. 当通过文件、网络来读取序列化后的对象时,必须按实际的写入顺序读取。

Java对象序列化详解相关推荐

  1. Java对象序列化详解6,Java对象的序列化与反序列化详解

    把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种途径: Ⅰ . 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中 Ⅱ.  在网 ...

  2. 64位JVM的Java对象头详解

    关注"Java艺术"一起来充电吧! 我们编写一个Java类,编译后会生成.class文件,当类加载器将class文件加载到jvm时,会生成一个Klass类型的对象(c++),称为类 ...

  3. Java基础:对象序列化详解

    在我们平日开发中,经常让PO类去实现Serializable接口,然后让其可序列化.不过有时我们并不是特别清楚为什么要序列化,特别是对于纯Web项目开发的同学来说,需求环境不一定能用上.下面我简单和大 ...

  4. 【java IO序列化详解】

    唐门崛起 序列化 1.对象序列化和反序列化 2.JDK类库中的序列化API 3.为什么实现了Serializable接口,就可以被序列化 4.serialVersionUID 4.1 serialVe ...

  5. JavaScript之对象序列化详解

    一.什么是对象序列化? 对象序列化是指将对象的状态转换为字符串(来自我这菜鸟的理解,好像有些书上也是这么说的,浅显易懂!): 序列化(Serialization)是将对象的状态信息转换为可以存储或传输 ...

  6. Java对象初始化详解

    在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.本文试图对Java如何执行对象的初始化做一个详细深入地介绍(与对象初始化相同,类在被加载之后也是需要初始化的,本 ...

  7. Java对象深拷贝详解(List深拷贝)

    1.Java中拷贝的概念 在Java语言中,拷贝一个对象时,有浅拷贝与深拷贝两种 浅拷贝:只拷贝源对象的地址,所以新对象与老对象共用一个地址,当该地址变化时,两个对象也会随之改变. 深拷贝:拷贝对象的 ...

  8. java对象克隆详解

    概述: 当我们new一个对象时,其中的属性就会被初始化, 那么想要保存刚开始初始化的值就靠clone方法来实现, 平时我们最常见的是一个对象的引用指向另一个对象,并不是创建了两个对象. Person ...

  9. Java 对象排序详解

    很难想象有Java开发人员不曾使用过Collection框架.在Collection框架中,主要使用的类是来自List接口中的ArrayList,以及来自Set接口的HashSet.TreeSet,我 ...

最新文章

  1. 机器翻译注意力机制及其PyTorch实现
  2. 【经典概念】一文详解Batch Normalization!!!
  3. 3年完成2款云端AI芯片研发量产,百度造芯为什么这么快?
  4. 最大公因数、最小公倍数、因式分解
  5. javafx性能_对JavaFX Mobile应用程序进行性能分析
  6. java文件名命名的规则,Java文件名及其他命名规则
  7. 【Docker】docker bash: sudo: command not found
  8. C++ 四种智能指针详解
  9. 什么是应用管理与运维平台(ServiceStage)?
  10. java webservice wsimport 无法将名称 'soapenc:Array' 解析为 'type definition' 组件 时对应的解决方法...
  11. python线程池的使用
  12. 用户自定义变量、系统变量、环境变量
  13. 电力猫引起的OpenWrt路由器死机掉线的原因
  14. 小游戏策划案例精选_小游戏活动策划案?
  15. 如何在CSDN上上传资源
  16. Keil的AC6与AC5中文手册
  17. 页面最上方的搜索和logo叫什么_网页顶部导航栏设计总结
  18. 跨次元!目标检测类别超20000!
  19. 项目管理之产品交付1
  20. 3d album android下载,声影制作家3D-Album

热门文章

  1. [HOW TO]-从github拉取optee代码拉不下来怎么办?
  2. MySQL外键关联(一对多)MySQL连接查询
  3. 一次SSH爆破攻击haiduc工具的应急响应
  4. MySQL创建数据表(CREATE TABLE语句)
  5. 1082 Read Number in Chinese (25 分)【难 / 模拟 字符串】
  6. 2021暑假每日一题 【week9 完结】
  7. MySQL中的数据分组
  8. SqlServer数据类型
  9. 奖客富翁系统python_作业 2018-12-28 20.1 奖客富翁
  10. 【PAT】A1028 List Sorting