Java序列化——實現Serializbale接口

1. 理論

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

假如  UserInfo 實現了 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 接口之因此定義爲空,是由於它只起到了一個標識的做用,告訴程序實現了它的對象是能夠被序列化的,但真正序列化和反序列化的操做並不須要它來完成。

注意事項:
1.  static 和  transient 修飾的字段是不會被序列化的。
  證實:在UserInfo類裏面加入一個字段:  private static int Age; 
  而後跑一邊程序,發現輸出的文件中是沒有 Age的內容的;
  爲何出現這種現象呢?
  由於,序列化保存的是對象的狀態,而 static 修飾的屬性是屬於類的狀態,因此序列化是不會序列 static 修飾的屬性;
 
除了  Serializable 以外,Java 還提供了一個序列化接口  Externalizable
有什麼不一樣呢?
首先,把 UserInfo 類實現的接口  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 }
相關文章
相關標籤/搜索