定义
Java序列化是指将Java对象保存为二进制字节码的过程;Java反序列化表示将二级制字节码转化为Java对象的过程。
Java序列化的原因
- 由于Java对象的生命周期比JVM短,JVM停止运行之后,Java对象就不复存在,如果想要在JVM停止运行之后,获取Java对象,就需要对其进行序列化后进行保存。
- 需要将Java对象通过网络传输时(rmi),通过将Java对象序列化为二进制的形式在网络中传输。
Java序列化的原理
可以通过
ObjectInputStream
和ObjectOutputStream
的readObject()
和writeObject()
方法进行反序列化和序列化。实现代码:
待序列化对象
@Data @ToString public class LearnDTO implements Serializable { private String name; private int age; private String nation; }
序列化方法
public class SerializableLearning { public static void main(String[] args) { LearnDTO dto = new LearnDTO(); dto.setName("gavin"); dto.setAge(18); dto.setNation("汉"); System.out.println("序列化对象----开始"); writeObject(dto); System.out.println("序列化对象----结束"); System.out.println("反序列化对象----开始"); readObject(); System.out.println("反序列化对象----结束"); } public static void writeObject(LearnDTO dto) { try (FileOutputStream file = new FileOutputStream(new File("test")); ObjectOutputStream os = new ObjectOutputStream(file)) { os.writeObject(dto); } catch (IOException e) { e.printStackTrace(); } } public static void readObject() { try (FileInputStream file = new FileInputStream(new File("test")); ObjectInputStream oi = new ObjectInputStream(file)) { LearnDTO o = (LearnDTO) oi.readObject(); System.out.println(o.toString()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
运行结果
序列化对象----开始 序列化对象----结束 反序列化对象----开始 LearnDTO(name=gavin, age=18, nation=汉) 反序列化对象----结束
序列化的对象必须实现
java.io.Serializable
接口。ObjectOutputStream在实现序列化的方法中,限制了序列化对象必须是
String
、Array
、Enum
以及实现了Serializable
接口的类型。因此自定义的对象要进行序列化必须实现java.io.Serializable
接口。源代码
// remaining cases if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } }
在序列化对象时,如果有一些变量的值不想被记录下来,可以通过
static
(静态变量)或者transient
(瞬态变量)关键词修饰变量。注意:static
变量 反序列化的值为 类中被赋予的初始值。源代码
Java序列化中通过
ObjectStreamClass
保存序列化对象的信息,在通过对象class初始化ObjectStreamClass
对象时,通过getDefaultSerialFields
方法保存要序列化的字段,此时会检查字段是否被static
、transient
关键词修饰。private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) { Field[] clFields = cl.getDeclaredFields(); ArrayList<ObjectStreamField> list = new ArrayList<>(); // Modifier.STATIC | Modifier.TRANSIENT int mask = Modifier.STATIC | Modifier.TRANSIENT; for (int i = 0; i < clFields.length; i++) { if ((clFields[i].getModifiers() & mask) == 0) { list.add(new ObjectStreamField(clFields[i], false, true)); } } int size = list.size(); return (size == 0) ? NO_FIELDS : list.toArray(new ObjectStreamField[size]); }
在待序列化的对象中通常要指定
serialVersionUID
值。否则当你改动对象的任何字段后,改动前就已经保存的序列化对象将无法再进行反序列化,在反序列化时将抛出java.io.InvalidClassException
异常。源代码
在
readObject()
反序列化方法内,通过读取序列化文件的数据获取 sid的值和序列化对象的sid的值进行一致性判断。if (model.isEnum != osc.isEnum) { throw new InvalidClassException(model.isEnum ? "cannot bind enum descriptor to a non-enum class" : "cannot bind non-enum descriptor to an enum class"); } if (model.serializable == osc.serializable && !cl.isArray() && suid != osc.getSerialVersionUID()) { throw new InvalidClassException(osc.name, "local class incompatible: " + "stream classdesc serialVersionUID = " + suid + ", local class serialVersionUID = " + osc.getSerialVersionUID()); }
通过实现
java.io.Externalizable
接口,然后重写writeExternal()
和readExternal()
方法,可以自定义序列化以及反序列化的方式。Externalizable
接口继承了Serializable
接口。在ObjectInputStream
和ObjectOutputStream
进行序列化和反序列化时会判断是否实现此接口,从而决定是否调用重写的writeExternal()
和readExternal()
方法。源代码
ObjectOutputStream.writeOrdinaryObject
方法中判断是否实现Externalizable
接口。并在writeExternalData
方法中调用重写的writeExternal
方法。private void writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) throws IOException { ... if (desc.isExternalizable() && !desc.isProxy()) { writeExternalData((Externalizable) obj); } else { writeSerialData(obj, desc); } ... }
Java序列化的缺点
- Java自身的序列化功能,必须和Java对象类型绑定。如果反序列化的项目没有对应的Java类型,则在反序列化时就会抛出
ClassNotFoundException
异常。这大大限制了Java序列化的使用场景。 - Java序列化的数据流通过
byte[]
数组传输,生成的字节数组流太大不仅占用内存空间,而且不利于进行网络传输。
针对Java序列化的缺点,项目中很少使用Java序列化的功能,在设计对象序列化时通常采用第三方的序列化框架,常用的序列化工具有:转JSON类工具、Hessian、Kryo、Xstream、Protobuf等。
具体的序列化工具分析可以参考一下文章:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。