[Java] 序列化之基本认识

你的名字 2022-05-08 10:24 177阅读 0赞

一、简介

Java 序列化即将 Java 对象转换为二进制序列的过程,主要用于网络通信、持久化存储。

二、实现方式

Serializable

Java 中实现 Serializable 接口即可实现序列化。

Externalizable

Serializable 的子类,实现类需实现 writeExternal(), readExternal() 方法,和实现 writeObject(), readObject() 类似,可自行修改序列化或反序列化后的对象的信息:
Externalizable

三、主要特性

serialVersionUID

两个类的序列化ID不同,则无法相互序列化与反序列化。
要点:

  1. 可显示指定 serialVersionUID: 指定固定值或随机生成
  2. 未指定时,会默认生成一个 serialVersionUID: 根据类信息自动生成

序列化ID不一致

SuperClass 类:

  1. import java.io.Serializable;
  2. /** * @author wengliemiao */
  3. public class SuperClass implements Serializable {
  4. private static final long serialVersionUID = 1L;
  5. private String name = "";
  6. private String age = "";
  7. public String getName() {
  8. return name;
  9. }
  10. public void setName(String name) {
  11. this.name = name;
  12. }
  13. }

SerialVersionTest 测试类:

  1. package com.wlm.jdk.serialVersion;
  2. import java.io.*;
  3. /** 1. @author wengliemiao */
  4. public class SerialVersionTest {
  5. public static void main(String[] args) {
  6. SuperClass superClass = new SuperClass();
  7. superClass.setName("2333");
  8. try {
  9. ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("/Users/wengliemiao/Documents/timerange_test7"));
  10. oo.writeObject(superClass);
  11. oo.close();
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. }
  15. /* try { ObjectInputStream oi = new ObjectInputStream(new FileInputStream("/Users/wengliemiao/Documents/timerange_test7")); SuperClass superClass1 = (SuperClass) oi.readObject(); System.out.println(superClass1.getName()); oi.close(); } catch (Exception e) { e.printStackTrace(); } */
  16. }
  17. }

操作步骤:

  1. SuperClass 类的 serialVersionUID 为1L时,执行 writeObject()
  2. SuperClass 类的 serialVersionUID 改为2L,执行 readObject();

结果为:
修改serialVersionUID

未指定序列化ID

类未执行序列化ID时,会自动生成一个,后续如果修改了类信息,则会导致序列化ID不一致,从而导致反序列化失败。
执行步骤:

  1. SuperClass 类信息除不指定 serialVersionUID 外,其他信息和前面一致,执行 writeObject()
  2. SuperClass 类随意增加一个字段,执行 readObject();

结果为:
未指定序列化ID

静态变量序列化

静态变量无法被序列化,因为序列化保存的是对象的状态,而静态变量是类的状态。
验证步骤:

  1. SuperClass 类增加静态变量b = 10;
  2. 执行序列化操作,并设置 b = 20;
  3. 执行反序列化操作,并输出 b 的值;

测试 main 方法为:

  1. public static void main(String[] args) {
  2. SuperClass superClass = new SuperClass();
  3. try {
  4. System.out.println("序列化前: " + superClass.b);
  5. ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("/Users/wengliemiao/Documents/timerange_test9"));
  6. oo.writeObject(superClass);
  7. oo.close();
  8. } catch (IOException e) {
  9. e.printStackTrace();
  10. }
  11. superClass.b = 20;
  12. try {
  13. ObjectInputStream oi = new ObjectInputStream(new FileInputStream("/Users/wengliemiao/Documents/timerange_test9"));
  14. SuperClass superClass1 = (SuperClass) oi.readObject();
  15. System.out.println("反序列化后: " + superClass1.b);
  16. oi.close();
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }
  20. }

结果为:
静态变量序列化

父类序列化

如果子类实现 Serializable 接口,而父类未实现,则序列化与反序列化时,父类数据不会参与。验证如下:
SuperClass 类:

  1. public class SuperClass {
  2. private String name = "";
  3. public String getName() {
  4. return name;
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. }

SubClass 类:

  1. package com.wlm.jdk.serialVersion;
  2. import java.io.Serializable;
  3. /** * @author wengliemiao */
  4. public class SubClass extends SuperClass implements Serializable {
  5. private static final long serialVersionUID = -1681989450035759750L;
  6. private String address = "";
  7. public String getAddress() {
  8. return address;
  9. }
  10. public void setAddress(String address) {
  11. this.address = address;
  12. }
  13. }

测试类如下:

  1. public static void main(String[] args) {
  2. SubClass subClass = new SubClass();
  3. subClass.setName("wlm");
  4. subClass.setAddress("火车东站");
  5. try {
  6. ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("/Users/wengliemiao/Documents/timerange_test11"));
  7. oo.writeObject(subClass);
  8. oo.close();
  9. } catch (IOException e) {
  10. e.printStackTrace();
  11. }
  12. try {
  13. ObjectInputStream oi = new ObjectInputStream(new FileInputStream("/Users/wengliemiao/Documents/timerange_test11"));
  14. SubClass subClass1= (SubClass) oi.readObject();
  15. System.out.println("姓名: " + subClass1.getName());
  16. System.out.println("地址: " + subClass1.getAddress());
  17. oi.close();
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }
  21. }

此时输出数据为:
父类未实现Serializable
而为父类实现 Serializable 接口后,输出为:
父类实现Serializable

transient 关键字

transient 关键字的作用则是阻止变量的序列化,被反序列化后,transient 变量的值被设为初始值。验证如下, Subclass 类中增加 transient 变量:

  1. private transient String sex;

其他代码与前面一致,输出为:
transient

writeObject(), readObject()

可通过实现 writeObject(), readObject() 方法来进行自定义的序列化和反序列化。如果未实现,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。

如 JDK 中 ArrayList 类,elementData[] 字段是 transient 类型,不会参与序列化:

  1. /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */
  2. transient Object[] elementData; // non-private to simplify nested class access

ArrayList 通过实现 writeObject(), readObject() 方法,序列化和反序列化 elementData[] 中的数据:

  1. /** * Save the state of the <tt>ArrayList</tt> instance to a stream (that * is, serialize it). * * @serialData The length of the array backing the <tt>ArrayList</tt> * instance is emitted (int), followed by all of its elements * (each an <tt>Object</tt>) in the proper order. */
  2. private void writeObject(java.io.ObjectOutputStream s)
  3. throws java.io.IOException{
  4. // Write out element count, and any hidden stuff
  5. int expectedModCount = modCount;
  6. s.defaultWriteObject();
  7. // Write out size as capacity for behavioural compatibility with clone()
  8. s.writeInt(size);
  9. // Write out all elements in the proper order.
  10. for (int i=0; i<size; i++) {
  11. s.writeObject(elementData[i]);
  12. }
  13. if (modCount != expectedModCount) {
  14. throw new ConcurrentModificationException();
  15. }
  16. }
  17. /** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). */
  18. private void readObject(java.io.ObjectInputStream s)
  19. throws java.io.IOException, ClassNotFoundException {
  20. elementData = EMPTY_ELEMENTDATA;
  21. // Read in size, and any hidden stuff
  22. s.defaultReadObject();
  23. // Read in capacity
  24. s.readInt(); // ignored
  25. if (size > 0) {
  26. // be like clone(), allocate array based upon size not capacity
  27. ensureCapacityInternal(size);
  28. Object[] a = elementData;
  29. // Read in all elements in the proper order.
  30. for (int i=0; i<size; i++) {
  31. a[i] = s.readObject();
  32. }
  33. }
  34. }

参考资料:
Java 序列化的高级认识
深入理解 JAVA 反序列化漏洞
序列化和反序列化

发表评论

表情:
评论列表 (有 0 条评论,177人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java序列与反序列

     Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程  为什么需要序列化与反序列化  我们知道,当两个进程