关于java:Java序列化浅析

定义

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 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());
        }
    }
  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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理