Java序列化和反序列化--jdk的序列化和反序列化方式

基本概念

  • 序列化:把存储在内存中的数据,转换为可传输数据流的过程,以便进行网络传输或者保存到本地
  • 反序列化:把流中的数据,转换成对象的形式
  • 原因:在服务之间调用,或者rpc之间调用时,对象是如何进行传输的,那么就是通过序列化转变为可以传输的流,进行交互。

基本案例

  • 实体类都是User
  • 工具类为SerialUtil
    public class SerialUtil {
    
    
        public <T> byte[] serialize(T obj){
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
    
            try {
                ObjectOutputStream objectOutputStream =
                        new ObjectOutputStream(bos);
                objectOutputStream.writeObject(obj);
                return bos.toByteArray();
            }catch (Exception e){
                e.printStackTrace();
            }
    
            return new byte[0];
        }
    
    
        public <T> byte[] serializeToFile(T obj){
    
            try {
                ObjectOutputStream objectOutputStream =
                        new ObjectOutputStream(new FileOutputStream(new File("user")));
                objectOutputStream.writeObject(obj);
            }catch (Exception e){
                e.printStackTrace();
            }
    
            return new byte[0];
        }
    
    
        public <T> T deserialize(byte[] data){
            ByteArrayInputStream bis = new ByteArrayInputStream(data);
    
            try {
                ObjectInputStream objectInputStream =
                        new ObjectInputStream(bis);
                return (T)objectInputStream.readObject();
            }catch (Exception e){
                e.printStackTrace();
            }
            return null;
        }
    
    
        public <T> T deserializeFromFile(Class<T> clazz){
            try {
                ObjectInputStream objectInputStream =
                        new ObjectInputStream(new FileInputStream(new File("user")));
                return (T)objectInputStream.readObject();
            }catch (Exception e){
                e.printStackTrace();
            }
            return null;
        }
    }
    

情况一:实体类实现接口Serializable

  • 直接报错
    java.io.NotSerializableException: learing.serial.User
    	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    	at learing.serial.SerialUtil.serialzie(SerialUtil.java:14)
    	at learing.serial.Test.main(Test.java:16)
    
  • 原因:对于每个需要进行序列化的实体类,必须实现此接口,才能被JVM认可,需要进行序列化,是一种规范

情况二:序列化前后的序列化serialVersionUID不一致

  • 序列化前的serialVersionUID = 1L,序列化后的文件,是一个二进制文件
    �� sr learing.serial.User       
     L aget Ljava/lang/Integer;L namet Ljava/lang/String;
    xpsr java.lang.Integer⠤���8 I valuexr java.lang.Number������  xp   dt test
    
  • 反序列化时serialVersionUID = 2L,反序列化,直接报错,含义:类文件不一致:流中的序列化id为1 ,本地的序列化id为2,对于序列化id可以认为是进行一次类文件的校验,表示是否被修改过,如果修改了则序列化id不一样,则不能被进行反序列化。
    java.io.InvalidClassException: learing.serial.User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
    	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
    	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
    	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
    	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
    	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
    	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
    	at learing.serial.SerialUtil.deserializeFromFile(SerialUtil.java:56)
    	at learing.serial.Test.main(Test.java:17)
    

情况三:序列化前后字段不一致

  • 序列化后增加一个address字段,serialVersionUID不变,猜想,因为serialVersionUID没有发生改变,则认为原类文件没有发生过改变,则可以正常反序列化,没有的字段的值,自动转换为默认值。
  • 结果和猜想一致

情况四:序列化前后不进行serialVersionUID的赋值

操作
  • 序列化前不进行赋值
  • 反序列化前增加字段
结果
  • 报类文件不一致错误
    java.io.InvalidClassException: learing.serial.User; local class incompatible: stream classdesc serialVersionUID = 5586612210164721760, local class serialVersionUID = 1400544103000078881
    	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
    	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
    	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
    	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
    	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
    	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
    	at learing.serial.SerialUtil.deserializeFromFile(SerialUtil.java:56)
    	at learing.serial.Test.main(Test.java:17)
    
结论
  • 对于指定了serialVersionUID,不管是类文件如何变化都认为是同一个类文件,只不过,没有对应字段时,无法赋值
  • 对于没有指定serialVersionUID的,如果类发生了字段或者其他的变化,那么serialVersionUID就会变化导致无法反序列化成功
  • serialVersionUID表示一个类文件是否被修改过,修改过则不可以进行反序列化
  • 极端测试
    • 一开始存在两个字段,进行序列化
    public class User implements Serializable {
    
        private static final long serialVersionUID = 1187673335167113451L;
    
        private Integer age;
    
        private String name;
        
        //get set方法
    }
    
    • 序列化成功之后,保持serialVersionUID不变,删除所有字段,增加一个address字段,进行反序列化
    public class User implements Serializable {
    
    
        private static final long serialVersionUID = 1187673335167113451L;
    
        private int address;
     
        //set get方法   
    }    
    
    • 结果:更加说明了,如果serialVersionUID不变,则表示文件未被修改,可以进行反序列化
    User{address=0}
    

情况五:类文件名称一致,但是不同文件

  • 类全路径名称不一致,但是类名一致,因为是不同的文件,不管是serialVersionUID一样还是其他信息,都不可以进行序列化和反序列化,因为序列化和反序列化都是针对的同一类文件进行的。
  • 不一致会报错
    Exception in thread "main" java.lang.ClassCastException: learing.serial.User cannot be cast to learing.serial.dto.User
    	at learing.serial.Test.main(Test.java:16)
    
  • serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
      类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。
  • 显式地定义serialVersionUID有两种用途:
    • 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
    • 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

缺点

  • 不可以跨平台
  • 数据比较其他序列化方法大

问题

  1. 如果被transient修饰了,还可以进行序列化吗?
    • 可以的,只要实现的writeObject和readObject方法即可以自定义序列化
  2. 1这样做的原因呢?
    • 因为对于List而言,里面的对象,可能存在几个,如果直接将内部的List进行序列化,会有空间浪费,影响带宽,其次,还有一些属性是通过该属性获取的,好比,list的长度,那么自定义反序列化则可以通过反序列化List中的具体元素个数,进而设置长度,减少传输的数据大小,提高性能。
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页