定义

Java序列化是指将Java对象保留为二进制字节码的过程;Java反序列化示意将二级制字节码转化为Java对象的过程。

Java序列化的起因

  1. 因为Java对象的生命周期比JVM短,JVM进行运行之后,Java对象就不复存在,如果想要在JVM进行运行之后,获取Java对象,就须要对其进行序列化后进行保留。
  2. 须要将Java对象通过网络传输时(rmi),通过将Java对象序列化为二进制的模式在网络中传输。

Java序列化的原理

  1. 能够通过ObjectInputStreamObjectOutputStreamreadObject() 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=汉)    反序列化对象----完结
  2. 序列化的对象必须实现 java.io.Serializable接口。

    ObjectOutputStream在实现序列化的办法中,限度了序列化对象必须是StringArrayEnum以及实现了Serializable接口的类型。因而自定义的对象要进行序列化必须实现java.io.Serializable接口。

    源代码

    // remaining casesif (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());    }}
  3. 在序列化对象时,如果有一些变量的值不想被记录下来,能够通过static(动态变量)或者 transient(瞬态变量)关键词润饰变量。留神:static变量 反序列化的值为 类中被赋予的初始值。

    源代码

    Java序列化中通过ObjectStreamClass保留序列化对象的信息,在通过对象class初始化ObjectStreamClass对象时,通过getDefaultSerialFields办法保留要序列化的字段,此时会查看字段是否被statictransient关键词润饰。

    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]);}
  4. 在待序列化的对象中通常要指定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());}
  5. 通过实现java.io.Externalizable接口,而后重写writeExternal()readExternal()办法,能够自定义序列化以及反序列化的形式。Externalizable接口继承了Serializable接口。在ObjectInputStreamObjectOutputStream进行序列化和反序列化时会判断是否实现此接口,从而决定是否调用重写的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序列化的毛病

  1. Java本身的序列化性能,必须和Java对象类型绑定。如果反序列化的我的项目没有对应的Java类型,则在反序列化时就会抛出ClassNotFoundException异样。这大大限度了Java序列化的应用场景。
  2. Java序列化的数据流通过byte[]数组传输,生成的字节数组流太大不仅占用内存空间,而且不利于进行网络传输。

针对Java序列化的毛病,我的项目中很少应用Java序列化的性能,在设计对象序列化时通常采纳第三方的序列化框架,罕用的序列化工具备:转JSON类工具、Hessian、Kryo、Xstream、Protobuf等。

具体的序列化工具剖析能够参考一下文章:

https://juejin.im/post/6844903918879637518#heading-5