1. 理论
Java 序列化是 JDK 1.1 时的特性:将 Java 对象转换为字节数组,便于存储或传输。此后,仍然可以将字节数组转换回 Java 对象原有的状态。
序列化的思想是“冻结”对象状态,传输对象状态(写到磁盘、通过网络传输等等);
反序列化的思想是“解冻”对象状态,重新获得可用的 Java 对象。
所有这些事情的发生要归功于 ObjectInputStream/ObjectOutputStream 类、完全保真的元数据以及程序员愿意用 Serializable 标识接口标记他们的类,从而 “参与” 这个过程。
再来看看序列化 Serializbale
接口的定义:
public interface Serializable { }
明明就一个空的接口嘛,为什么能够保证实现了它的“类的对象”被序列化和反序列化?
在回答上述问题之前,我们先来创建一个类(只有两个字段,和对应的 getter/setter
),用于序列化和反序列化。
1 public class UserInfo { 2 3 private int UserId; 4 5 6 private String UserName; 7 8 9 public int getUserId() { 10 return UserId; 11 } 12 13 public void setUserId(int userId) { 14 UserId = userId; 15 } 16 17 public String getUserName() { 18 return UserName; 19 } 20 21 public void setUserName(String userName) { 22 UserName = userName; 23 } 24 }
再来创建一个测试类,通过 ObjectOutputStream
将对象信息写入到文件当中,实际上就是一种序列化的过程;再通过 ObjectInputStream
将对象信息从文件中读出来,实际上就是一种反序列化的过程。
1 import java.io.*; 2 3 public class Main { 4 5 public static void main(String[] args) { 6 WriteIntoFile(); 7 System.out.println("Hello World!"); 8 } 9 private static void WriteIntoFile(){ 10 11 UserInfo userInfo = new UserInfo(); 12 userInfo.setUserId(12); 13 userInfo.setUserName("Jerry"); 14 15 // 把对象写到文件中 16 try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("12jerry"));){ 17 objectOutputStream.writeObject(userInfo); 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 22 // 从文件中读出对象 23 try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("12jerry")));){ 24 UserInfo userInfo1 = (UserInfo) objectInputStream.readObject(); 25 System.out.println(userInfo1); 26 } catch (IOException | ClassNotFoundException e) { 27 e.printStackTrace(); 28 } 29 } 30 }
不过,由于 UserInfo没有实现 Serializbale
接口,所以在运行测试类的时候会抛出异常,堆栈信息如下:
"C:Program FilesJavajdk1.8.0_211injava.exe" "-javaagent:D:Program FilesJetBrainsIntelliJ IDEA 2019.1.3libidea_rt.jar=34706:D:Program FilesJetBrainsIntelliJ IDEA 2019.1.3in" -Dfile.encoding=UTF-8 -classpath "C:Program FilesJavajdk1.8.0_211jrelibcharsets.jar;C:Program FilesJavajdk1.8.0_211jrelibdeploy.jar;C:Program FilesJavajdk1.8.0_211jrelibextaccess-bridge-64.jar;C:Program FilesJavajdk1.8.0_211jrelibextcldrdata.jar;C:Program FilesJavajdk1.8.0_211jrelibextdnsns.jar;C:Program FilesJavajdk1.8.0_211jrelibextjaccess.jar;C:Program FilesJavajdk1.8.0_211jrelibextjfxrt.jar;C:Program FilesJavajdk1.8.0_211jrelibextlocaledata.jar;C:Program FilesJavajdk1.8.0_211jrelibext ashorn.jar;C:Program FilesJavajdk1.8.0_211jrelibextsunec.jar;C:Program FilesJavajdk1.8.0_211jrelibextsunjce_provider.jar;C:Program FilesJavajdk1.8.0_211jrelibextsunmscapi.jar;C:Program FilesJavajdk1.8.0_211jrelibextsunpkcs11.jar;C:Program FilesJavajdk1.8.0_211jrelibextzipfs.jar;C:Program FilesJavajdk1.8.0_211jrelibjavaws.jar;C:Program FilesJavajdk1.8.0_211jrelibjce.jar;C:Program FilesJavajdk1.8.0_211jrelibjfr.jar;C:Program FilesJavajdk1.8.0_211jrelibjfxswt.jar;C:Program FilesJavajdk1.8.0_211jrelibjsse.jar;C:Program FilesJavajdk1.8.0_211jrelibmanagement-agent.jar;C:Program FilesJavajdk1.8.0_211jrelibplugin.jar;C:Program FilesJavajdk1.8.0_211jrelib esources.jar;C:Program FilesJavajdk1.8.0_211jrelib t.jar;E:IdeaProjectsTestSeroutproductionTestSer" Main java.io.NotSerializableException: UserInfo at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at Main.WriteIntoFile(Main.java:20) at Main.main(Main.java:6) java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: UserInfo at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1577) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) at Main.WriteIntoFile(Main.java:27) at Main.main(Main.java:6) Caused by: java.io.NotSerializableException: UserInfo at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at Main.WriteIntoFile(Main.java:20) ... 1 more Hello World! Process finished with exit code 0
顺着堆栈信息,我们来看一下 ObjectOutputStream
的 writeObject0()
方法。其部分源码如下:
1 if (obj instanceof String) { 2 writeString((String) obj, unshared); 3 } else if (cl.isArray()) { 4 writeArray(obj, desc, unshared); 5 } else if (obj instanceof Enum) { 6 writeEnum((Enum<?>) obj, desc, unshared); 7 } else if (obj instanceof Serializable) { 8 writeOrdinaryObject(obj, desc, unshared); 9 } else { 10 if (extendedDebugInfo) { 11 throw new NotSerializableException( 12 cl.getName() + " " + debugInfoStack.toString()); 13 } else { 14 throw new NotSerializableException(cl.getName()); 15 } 16 }
也就是说,ObjectOutputStream
在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 Serializable
,如果全都不是的话,抛出 NotSerializableException
。
Serializable
接口,就可以序列化和反序列化了。
1 import java.io.Serializable; 2 3 public class UserInfo implements Serializable { 4 private int UserId; 5 private String UserName; 6 public int getUserId() { 7 return UserId; 8 } 9 public void setUserId(int userId) { 10 UserId = userId; 11 } 12 public String getUserName() { 13 return UserName; 14 } 15 public void setUserName(String userName) { 16 UserName = userName; 17 } 18 19 }
具体序列化的过程如下:
以 ObjectOutputStream
为例,它在序列化的时候会依次调用 writeObject()
→writeObject0()
→writeOrdinaryObject()
→writeSerialData()
→invokeWriteObject()
→defaultWriteFields()
。
private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException { Class<?> cl = desc.forClass(); desc.checkDefaultSerialize(); int primDataSize = desc.getPrimDataSize(); desc.getPrimFieldValues(obj, primVals); bout.write(primVals, 0, primDataSize, false); ObjectStreamField[] fields = desc.getFields(false); Object[] objVals = new Object[desc.getNumObjFields()]; int numPrimFields = fields.length - objVals.length; desc.getObjFieldValues(obj, objVals); for (int i = 0; i < objVals.length; i++) { try { writeObject0(objVals[i], fields[numPrimFields + i].isUnshared()); } } }
那反序列化呢?反序列化的过程如下:
以 ObjectInputStream
为例,它在反序列化的时候会依次调用 readObject()
→readObject0()
→readOrdinaryObject()
→readSerialData()
→defaultReadFields()
。
1 private void defaultWriteFields(Object obj, ObjectStreamClass desc) 2 throws IOException 3 { 4 Class<?> cl = desc.forClass(); 5 desc.checkDefaultSerialize(); 6 7 int primDataSize = desc.getPrimDataSize(); 8 desc.getPrimFieldValues(obj, primVals); 9 bout.write(primVals, 0, primDataSize, false); 10 11 ObjectStreamField[] fields = desc.getFields(false); 12 Object[] objVals = new Object[desc.getNumObjFields()]; 13 int numPrimFields = fields.length - objVals.length; 14 desc.getObjFieldValues(obj, objVals); 15 for (int i = 0; i < objVals.length; i++) { 16 17 try { 18 writeObject0(objVals[i], 19 fields[numPrimFields + i].isUnshared()); 20 } 21 } 22 }
Serializable
接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。
static
和 transient
修饰的字段是不会被序列化的。Serializable
之外,Java 还提供了一个序列化接口 Externalizable
。Serializable
替换为 Externalizable
。1 package Model; 2 3 import java.io.*; 4 5 public class UserInfo implements Externalizable { 6 7 private int UserId; 8 private String UserName; 9 private static int Age=12; 10 11 public static int getAge() { 12 return Age; 13 } 14 public static void setAge(int age) { 15 Age = age; 16 } 17 public int getUserId() { 18 return UserId; 19 } 20 public void setUserId(int userId) { 21 UserId = userId; 22 } 23 public String getUserName() { 24 return UserName; 25 } 26 public void setUserName(String userName) { 27 UserName = userName; 28 } 29 30 public void writeExternal(ObjectOutput out) throws IOException { 31 32 } 33 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 34 35 } 36 }