Java基础 - Serializable序列化简介
参考:https://blog.csdn.net/u011607686/article/details/78933856 https://www.ibm.com/developerworks/cn/java/j-5things1/ https://baijiahao.baidu.com/s?id=1633305649182361563&wfr=spider&for=pc
Serializable序列化简介
什么是序列化
序列化:将对象的状态信息转换为可以存储或传输的形式(字节序列)的过程。
反序列化:把字节序列恢复为对象的过程。
实际上,序列化的思想是 “冻结” 对象状态,传输对象状态(写到磁盘、通过网络传输等等),然后 “解冻” 状态,重新获得可用的 Java 对象。所有这些事情的发生有点像是魔术,这要归功于 ObjectInputStream
/ObjectOutputStream
类、完全保真的元数据以及程序员愿意用 Serializable
标识接口标记他们的类,从而 “参与” 这个过程。
一个简单的序列化/反序列化过程:
@Data public class SerialVO implements Serializable { private String str1; private String str2; }
public class SerializableTest { /** * 序列化 */ private static void serialize() throws Exception { SerialVO serialVO = new SerialVO(); serialVO.setStr1("string 1"); serialVO.setStr2("string 2"); //ObjectOutputStream对象输出流,将serialVO对象存储到文件中,完成对serialVO对象的序列化操作 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("/Users/iyoukeji/Desktop/test.txt"))); oos.writeObject(serialVO); System.out.println("对象序列化成功!"); oos.close(); } /** * 反序列化 */ private static void deserialize() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("/Users/iyoukeji/Desktop/test.txt"))); SerialVO serialVO = (SerialVO) ois.readObject(); System.out.println("对象反序列化成功!"); System.out.println(serialVO.toString()); } public static void main(String[] args) throws Exception { serialize(); deserialize(); } }
transient和static
transient:意为“临时的”。被transient修饰的变量在序列化的时候不会被保存到文件中,通过反序列化读取这个变量时不会有值。
static:被static修饰的变量也是不会被序列化的,因为只有堆内存会被序列化。所以静态变量会天生不会被序列化。
修改SerialVO如下:
@Data public class SerialVO implements Serializable { private String str1; private String str2; private static int num1 = 123; private transient String transientStr = "transient"; }
执行序列化/反序列化方法之后打印:
对象序列化成功! SerialVO(str1=string 1, str2=string 2, transientStr=transient) 对象反序列化成功! SerialVO(str1=string 1, str2=string 2, transientStr=null)
serialVersionUID
serialVersionUID字段表示类的序列化版本,用于反序列化时校验。
在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一致则反序列化成功。否则就说明当前类跟序列化后的类发生了变化,在反序列化时就会发生crash,并且会报出InvalidClassException。
如果类中没有这个字段,那么在运行时JVM会通过类名/方法名/属性等诸多因素计算一个值。
推荐用户给每个需要序列化的类明确指定一个serialVersionUID。因为默认的计算方式严重依赖于编译器的实现,可能导致反序列化的时候抛出InvalidClassException异常。
案例重现:
(1)先执行 serialize() 方法,再将SerialVO修改为如下:
@Data public class SerialVO implements Serializable { private String str1; private String str2; private String str3; }
最后执行 deserialize() 方法,发现程序报InvalidClassException异常。
(2)先将SerialVO修改为如下:
@Data public class SerialVO implements Serializable { private static final long serialVersionUID = 1L; private String str1; private String str2; }
再执行 serialize() 方法。然后将SerialVO修改为如下:
@Data public class SerialVO implements Serializable { private static final long serialVersionUID = 1L; private String str1; private String str2; private String str3; }
最后执行 deserialize() 方法,反序列化成功。
序列化并不安全
如果我们想要序列化一个对象,首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而反序列的过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()。
让 Java 开发人员诧异并感到不快的是,序列化二进制格式完全编写在文档中,并且完全可逆。实际上,只需将二进制序列化流的内容转储到控制台,就足以看清类是什么样子,以及它包含什么内容。
这对于安全性有着不良影响。例如,当通过 RMI 进行远程方法调用时,通过连接发送的对象中的任何 private 字段几乎都是以明文的方式出现在套接字流中,这显然容易招致哪怕最简单的安全问题。
幸运的是,序列化允许“hook”序列化过程,并在序列化之前和反序列化之后保护(或模糊化)字段数据。可以通过在 Serializable 对象上提供一个 writeObject 方法来做到这一点。
修改SerialVO如下:
@Data public class SerialVO implements Serializable { private static final long serialVersionUID = 1L; private int num1; private String str1; private String str2; private void writeObject(java.io.ObjectOutputStream stream) throws java.io.IOException { // "Encrypt"/obscure the sensitive data num1 = num1 << 2; stream.defaultWriteObject(); } private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException { stream.defaultReadObject(); // "Decrypt"/de-obscure the sensitive data num1 = num1 >> 2; } }
为了“hook”序列化过程,我们在SerialVO上实现一个writeObject
方法;为了“hook”反序列化过程,我们在同一个类上实现一个readObject
方法。