基本概念
- 序列化:把存储在内存中的数据,转换为可传输数据流的过程,以便进行网络传输或者保存到本地
- 反序列化:把流中的数据,转换成对象的形式
- 原因:在服务之间调用,或者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。
缺点
- 不可以跨平台
- 数据比较其他序列化方法大
问题
- 如果被transient修饰了,还可以进行序列化吗?
- 可以的,只要实现的writeObject和readObject方法即可以自定义序列化
- 1这样做的原因呢?
- 因为对于List而言,里面的对象,可能存在几个,如果直接将内部的List进行序列化,会有空间浪费,影响带宽,其次,还有一些属性是通过该属性获取的,好比,list的长度,那么自定义反序列化则可以通过反序列化List中的具体元素个数,进而设置长度,减少传输的数据大小,提高性能。