乐趣区

关于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

退出移动版