共计 4151 个字符,预计需要花费 11 分钟才能阅读完成。
定义
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 等。
具体的序列化工具剖析能够参考一下文章:
https://juejin.im/post/6844903918879637518#heading-5