Java 序列化是 JDK 1.1 時的特性:將 Java 對象轉換爲字節數組,便於存儲或傳輸。此後,仍然能夠將字節數組轉換回 Java 對象原有的狀態。java
序列化的思想是「凍結」對象狀態,傳輸對象狀態(寫到磁盤、經過網絡傳輸等等);程序員
反序列化的思想是「解凍」對象狀態,從新得到可用的 Java 對象。api
全部這些事情的發生要歸功於 ObjectInputStream/ObjectOutputStream 類、徹底保真的元數據以及程序員願意用 Serializable 標識接口標記他們的類,從而 「參與」 這個過程。數組
再來看看序列化 Serializbale
接口的定義:網絡
public interface Serializable { }
明明就一個空的接口嘛,爲何可以保證明現了它的「類的對象」被序列化和反序列化?ide
在回答上述問題以前,咱們先來建立一個類(只有兩個字段,和對應的 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
將對象信息從文件中讀出來,實際上就是一種反序列化的過程。idea
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
接口,因此在運行測試類的時候會拋出異常,堆棧信息以下:spa
"C:\Program Files\Java\jdk1.8.0_211\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar=34706:D:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_211\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\rt.jar;E:\IdeaProjects\TestSer\out\production\TestSer" 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()
方法。其部分源碼以下:debug
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() + "\n" + 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 }