JAVA基礎4---序列化和反序列化深入整理(JDK序列化)


一、什么是序列化和反序列化?

序列化:將對象狀態信息轉化成可以存儲或傳輸的形式的過程(Java中就是將對象轉化成字節序列的過程)

反序列化:從存儲文件中恢復對象的過程(Java中就是通過字節序列轉化成對象的過程)

 

二、為什么要序列化和反序列化?

Java中對象都是存儲在內存中,准確地說是JVM的堆或棧內存中,可以各個線程之間進行對象傳輸,但是無法在進程之間進行傳輸。另外如果需要在網絡傳輸中傳輸對象也沒有辦法,同樣內存中的對象也沒有辦法直接保存成文件。

所以需要對對象進行序列化,序列化對象之后一個個的Java對象就變成了字節序列,而字節序列是可以傳輸和存儲的。而反序列化就可以通過序列化生產的字節序列再恢復成序列化之前的對象狀態及信息。

總結:

1、進程之間傳輸對象(如RPC、RMI通信)

2、網絡通信時進行傳輸對象

3、持久化對象時需要將對象序列化

 

三、怎么序列化和反序列化?

實現序列化的方式有很多種,常用的方式有如下幾種:

3.1、JDK序列化

JDK序列化時JDK自帶的序列化方式,使用其他也比較方便,只需要序列化的類實現了Serializable接口即可,Serializable接口沒有定義任何方法和屬性,所以只是起到了標識的作用,表示這個類是可以被序列化的。

如果沒有實現Serializable接口而進行序列化操作就會拋出NotSerializableException異常。

能夠序列化的字段:屬性變量、父類的屬性變量(父類也需要實現Serializablie接口)

不能序列化的字段:靜態變量、父類的屬性變量、關鍵字transient修飾的變量、沒有實現Serializable接口的對象屬性

3.1.1、Serializable接口案例

定義類User、Person、Home、School分別如下

 1 public class Home implements Serializable {
 2     private String address;
 3 
 4     public String getAddress() {
 5         return address;
 6     }
 7 
 8     public void setAddress(String address) {
 9         this.address = address;
10     }
11 }
 1 public class School {
 2     private String schoolName;
 3 
 4     public String getSchoolName() {
 5         return schoolName;
 6     }
 7 
 8     public void setSchoolName(String schoolName) {
 9         this.schoolName = schoolName;
10     }
11 }

 

 1 public class Person implements Serializable {
 2 
 3     public static String parentType = "Person"; //父類靜態變量
 4 
 5     private String sex;//性別
 6 
 7     public String getSex() {
 8         return sex;
 9     }
10 
11     public void setSex(String sex) {
12         this.sex = sex;
13     }
14 }

 

public class User extends Person implements Serializable {
    public static boolean alive; //靜態變量
    private Long userId;//Long 類型
    private int age; //int 類型
    private String userName; //string 類型
    private String password; //string 類型
    private transient String IDCard; //不序列化的字段
    private Date birth; //Date類型
    private Home home; // 可以序列化的對象類型
    private School school; //不可以序列化的對象類型
    List<User> friends; //List類型

   /** set get 方法省略*/
}

 

案例中序列化的類為User,繼承類Person,分別含有類Home和School的對象屬性,序列化測試代碼如下:

 1 public class MainTest {
 2     public static void  main(String[] args) throws Exception{
 3         User user = new User();
 4         user.setAge(10);
 5         user.setBirth(new Date());
 6         user.setPassword("123456");
 7         user.setUserName("Jack");
 8         user.setUserId(100L);
 9         user.setSex("男");
10         user.setIDCard("131313131313113");
11         user.parentType = "son";//修改父類靜態變量
12         user.alive = true; //修改User類的靜態變量
13 
14         Home home = new Home();
15         home.setAddress("中國浙江");
16         School school = new School();
17         school.setSchoolName("清華大學");
18         user.setHome(home);//設置對象屬性
19 //        user.setSchool(school);//設置對象屬性 (因為School類沒有實現Seriliazable接口,所以如果設置就會報錯)
20 
21         List<User> friends = new ArrayList<User>();
22         User userF = new User();
23         userF.setUserId(101L);
24         userF.setUserName("Friend");
25         friends.add(userF);
26         user.setFriends(friends);
27 
28         //序列化
29         serializer(user);
30         //反序列化
31         User newUser = derializer();
32         //驗證
33         System.out.println("驗證兩個對象是否相等");
34         System.out.println("原對象地址:"+user.toString());
35         System.out.println("新對象地址:"+newUser.toString());
36         System.out.println("******************");
37         System.out.println("打印兩個對象");
38         System.out.println("原對象數據:"+JSON.toJSON(user).toString());
39         System.out.println("新對象數據:"+JSON.toJSON(newUser).toString());
40     }
41 
42     /**序列化對象*/
43     private static void serializer(User user)throws Exception{
44         ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/xxw/testlog/user.txt")));
45         outputStream.writeObject(user);
46         outputStream.close();
47     }
48 
49     /**反序列化對象*/
50     private static User derializer()throws Exception{
51         ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("/Users/xxw/testlog/user.txt")));
52         User user = (User) inputStream.readObject();
53         inputStream.close();
54         return user;
55     }
56 }

 

測試結果為:

1 驗證兩個對象是否相等
2 原對象地址:com.lucky.demo.base.seralizer.demo.User@3764951d
3 新對象地址:com.lucky.demo.base.seralizer.demo.User@4783da3f
4 ******************
5 打印兩個對象
6 原對象數據:{"iDCard":"131313131313113","password":"123456","sex":"男","birth":1573203467764,"userName":"Jack","userId":100,"age":10,"friends":[{"userName":"Friend","userId":101,"age":0}],"home":{"address":"中國浙江"}}
7 新對象數據:{"password":"123456","sex":"男","birth":1573203467764,"userName":"Jack","userId":100,"age":10,"friends":[{"userName":"Friend","userId":101,"age":0}],"home":{"address":"中國浙江"}}

 

這里User類的School屬性沒有實現Serializable接口,所以如果給school屬性賦值然后進行序列化就會報錯,結果如下:

1 Exception in thread "main" java.io.NotSerializableException: com.lucky.demo.base.seralizer.demo.School
2     at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
3     at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
4     at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
5     at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
6     at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
7     at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
8     at com.lucky.demo.base.seralizer.demo.MainTest.serializer(MainTest.java:59)
9     at com.lucky.demo.base.seralizer.demo.MainTest.main(MainTest.java:43)

 

而如果User類的父類Person沒有實現Serializable接口,那么序列化的時候不會報錯,但是父類中的屬性在反序列化之后字段就會沒有,結果如下:

1 打印兩個對象
2 原對象數據:{"iDCard":"131313131313113","password":"123456","sex":"男","birth":1573203839905,"userName":"Jack","userId":100,"age":10,"friends":[{"userName":"Friend","userId":101,"age":0}],"home":{"address":"中國浙江"}}
3 新對象數據:{"password":"123456","birth":1573203839905,"userName":"Jack","userId":100,"age":10,"friends":[{"userName":"Friend","userId":101,"age":0}],"home":{"address":"中國浙江"}}

 

這里就沒有了父類的屬性sex字段

 

3.1.2、Serializable接口實現原理

Serializable接口是一個空接口,沒有定義任何的方法和屬性,所以Serialiazable接口的作用就是起到一個標識的作用,源碼如下

1 public interface Serializable {
2 }

 

Serializable接口既然是標識的作用,那么就需要在實際序列化操作的時候進行識別,而實際的序列化操作是通過ObjectOutputStreamObjectInputStream實現的,那么接下來就看下這兩個類的是如何實現序列化和反序列化的

3.1.2.1、ObjectOutputStream源碼解析

構造函數如下

 1 public ObjectOutputStream(OutputStream out) throws IOException {
 2         verifySubclass();
 3         bout = new BlockDataOutputStream(out);
 4         handles = new HandleTable(10, (float) 3.00);
 5         subs = new ReplaceTable(10, (float) 3.00);
 6         enableOverride = false;
 7         writeStreamHeader();
 8         bout.setBlockDataMode(true);
 9         if (extendedDebugInfo) {
10             debugInfoStack = new DebugTraceInfoStack();
11         } else {
12             debugInfoStack = null;
13         }
14     }

 

OutoutStream表示保存的二進制流,也就是將序列化的對象保存到這個二進制流中,再看下具體的序列化方法源碼如下:

 1 public final void writeObject(Object obj) throws IOException {
 2         if (enableOverride) {//enableOverride 表示是否可以被覆蓋,默認為false
 3             writeObjectOverride(obj);
 4             return;
 5         }
 6         try {
 7             //執行具體的序列化操作
 8             writeObject0(obj, false);
 9         } catch (IOException ex) {
10             if (depth == 0) {
11                 writeFatalException(ex);
12             }
13             throw ex;
14         }
15     }

 

 最終執行了writeObject0(obj, false)方法,代碼如下:

 1 private void writeObject0(Object obj, boolean unshared)
 2             throws IOException
 3     {
 4         boolean oldMode = bout.setBlockDataMode(false);
 5         depth++;
 6         try {
 7             // handle previously written and non-replaceable objects
 8             // 處理已經處理過的和不可替換的對象,這些是不可序列化的
 9             int h;
10             if ((obj = subs.lookup(obj)) == null) {
11                 writeNull();
12                 return;
13             } else if (!unshared && (h = handles.lookup(obj)) != -1) {
14                 writeHandle(h);
15                 return;
16             } else if (obj instanceof Class) {
17                 writeClass((Class) obj, unshared);
18                 return;
19             } else if (obj instanceof ObjectStreamClass) {
20                 writeClassDesc((ObjectStreamClass) obj, unshared);
21                 return;
22             }
23 
24             // check for replacement object
25             Object orig = obj;
26             //獲取對象的Class對象
27             Class<?> cl = obj.getClass();
28             ObjectStreamClass desc;
29             for (;;) {
30                 // REMIND: skip this check for strings/arrays?
31                 Class<?> repCl;
32                 //獲取Class的描述信息,並且判斷是否是Serializable接口
33                 desc = ObjectStreamClass.lookup(cl, true);
34                 if (!desc.hasWriteReplaceMethod() ||
35                         (obj = desc.invokeWriteReplace(obj)) == null ||
36                         (repCl = obj.getClass()) == cl)
37                 {
38                     break;
39                 }
40                 cl = repCl;
41             }
42 
43             //如果允許被替換的情況
44             if (enableReplace) {
45                 Object rep = replaceObject(obj);
46                 if (rep != obj && rep != null) {
47                     cl = rep.getClass();
48                     desc = ObjectStreamClass.lookup(cl, true);
49                 }
50                 obj = rep;
51             }
52 
53             // if object replaced, run through original checks a second time
54             // 如果對象被替換了,只有是ObjectOutputStream的子類才會出現
55             if (obj != orig) {
56                 subs.assign(orig, obj);
57                 if (obj == null) {
58                     writeNull();
59                     return;
60                 } else if (!unshared && (h = handles.lookup(obj)) != -1) {
61                     writeHandle(h);
62                     return;
63                 } else if (obj instanceof Class) {
64                     writeClass((Class) obj, unshared);
65                     return;
66                 } else if (obj instanceof ObjectStreamClass) {
67                     writeClassDesc((ObjectStreamClass) obj, unshared);
68                     return;
69                 }
70             }
71 
72             // remaining cases
73             /**序列化不同類型的對象,分別序列化String、數組、枚舉類型
74              * 對於Integer、Long等都屬於實現了Serializable接口的數據類型*/
75            if (obj instanceof String) {//序列化字符串類型對象 76  writeString((String) obj, unshared); 77 } else if (cl.isArray()) {//序列化數組類型對象 78  writeArray(obj, desc, unshared); 79 } else if (obj instanceof Enum) {//序列化枚舉類型對象 80 writeEnum((Enum<?>) obj, desc, unshared); 81 } else if (obj instanceof Serializable) {//序列化實現了Serializable接口的數據類型 82  writeOrdinaryObject(obj, desc, unshared); 83 } else {//拋出不可序列化異常 84 if (extendedDebugInfo) { 85 throw new NotSerializableException( 86 cl.getName() + "\n" + debugInfoStack.toString()); 87 } else { 88 throw new NotSerializableException(cl.getName()); 89  } 90  } 91         } finally {
92             depth--;
93             bout.setBlockDataMode(oldMode);
94         }
95     }
96 }

 

前面都是在做各種檢查,實際有效的代碼就是從75行開始,分別針對不同類型的對象分別執行不同的序列化方法

writeString方法的邏輯就是將字符串按字節的方式進行序列化,底層就是通過數組復制的方式獲取到char[],然后寫入到緩存的序列化的byte[]數組中

1 System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);

 writeArray方法的邏輯就是先判斷數組的數據類型是什么,如果是基本數據類型之間寫入byte數字,如果是對象類型就調用writeObject0方法

writeEnum方法的邏輯是直接寫入枚舉的值

而對於對象類型是比較復雜的,也就是writeOrdinaryObject方法,邏輯如下:

 1 private void writeOrdinaryObject(Object obj,
 2                                      ObjectStreamClass desc,
 3                                      boolean unshared)
 4             throws IOException
 5     {
 6         if (extendedDebugInfo) {
 7             debugInfoStack.push(
 8                     (depth == 1 ? "root " : "") + "object (class \"" +
 9                             obj.getClass().getName() + "\", " + obj.toString() + ")");
10         }
11         try {
12             //檢查ObjectStreamClass對象
13             desc.checkSerialize();
14 
15             //寫入標記表示是Object類型
16             bout.writeByte(TC_OBJECT);
17             //寫入Class對象的描述信息
18             writeClassDesc(desc, false);
19             handles.assign(unshared ? null : obj);
20             if (desc.isExternalizable() && !desc.isProxy()) {
21                 //寫入實現了Externalizable接口的對象
22                 writeExternalData((Externalizable) obj);
23             } else {
24                 //寫入實現了Serializable
25                 writeSerialData(obj, desc);
26             }
27         } finally {
28             if (extendedDebugInfo) {
29                 debugInfoStack.pop();
30             }
31         }
32     }

 最終按實現了Externalizable接口或Serializable接口分別執行writeExternalData和writeSerialData方法,writeSerialData方法如下:

 1 private void writeSerialData(Object obj, ObjectStreamClass desc)
 2             throws IOException
 3     {
 4         //獲取類的描述信息對象
 5         ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
 6         for (int i = 0; i < slots.length; i++) {
 7             ObjectStreamClass slotDesc = slots[i].desc;
 8             //判斷該類是否自定義類writeObject方法,如果重寫了該方法則按重寫的邏輯處理
 9             if (slotDesc.hasWriteObjectMethod()) {
10                 ObjectOutputStream.PutFieldImpl oldPut = curPut;
11                 curPut = null;
12                 SerialCallbackContext oldContext = curContext;
13 
14                 if (extendedDebugInfo) {
15                     debugInfoStack.push(
16                             "custom writeObject data (class \"" +
17                                     slotDesc.getName() + "\")");
18                 }
19                 try {
20                     curContext = new SerialCallbackContext(obj, slotDesc);
21                     bout.setBlockDataMode(true);
22                     //通過反射的方式執行自定義的writeObejct方法
23                     slotDesc.invokeWriteObject(obj, this);
24                     bout.setBlockDataMode(false);
25                     bout.writeByte(TC_ENDBLOCKDATA);
26                 } finally {
27                     curContext.setUsed();
28                     curContext = oldContext;
29                     if (extendedDebugInfo) {
30                         debugInfoStack.pop();
31                     }
32                 }
33 
34                 curPut = oldPut;
35             } else {
36                 //如果沒有自定義writeObject方法則按默認的方法寫入屬性數據
37  defaultWriteFields(obj, slotDesc); 38             }
39         }
40     }

 

先是根據類的描述信息判斷是否自定義了序列化方法writeObejct方法,如果自定義了就通過反射執行invokeWriteObejct方法,如果沒有自定義則執行defaultWriteFields方法,defaultWriteFields方法邏輯如下:

 

 1 private void defaultWriteFields(Object obj, ObjectStreamClass desc)
 2             throws IOException
 3     {
 4         Class<?> cl = desc.forClass();
 5         //校驗對象的類信息是否和類描述信息一致
 6         if (cl != null && obj != null && !cl.isInstance(obj)) {
 7             throw new ClassCastException();
 8         }
 9 
10         //
11         desc.checkDefaultSerialize();
12 
13         int primDataSize = desc.getPrimDataSize();
14         if (primVals == null || primVals.length < primDataSize) {
15             primVals = new byte[primDataSize];
16         }
17         desc.getPrimFieldValues(obj, primVals);
18         bout.write(primVals, 0, primDataSize, false);
19 
20         ObjectStreamField[] fields = desc.getFields(false);//獲取所有屬性
21         Object[] objVals = new Object[desc.getNumObjFields()];//獲取對象類型屬性
22         int numPrimFields = fields.length - objVals.length;
23         desc.getObjFieldValues(obj, objVals);
24         for (int i = 0; i < objVals.length; i++) {//遍歷對象類型屬性數組
25             if (extendedDebugInfo) {
26                 debugInfoStack.push(
27                         "field (class \"" + desc.getName() + "\", name: \"" +
28                                 fields[numPrimFields + i].getName() + "\", type: \"" +
29                                 fields[numPrimFields + i].getType() + "\")");
30             }
31             try {
32                 //遞歸寫入對象類型的屬性
33                 writeObject0(objVals[i],
34                         fields[numPrimFields + i].isUnshared());
35             } finally {
36                 if (extendedDebugInfo) {
37                     debugInfoStack.pop();
38                 }
39             }
40         }
41     } 

總結:

序列化的整體邏輯就是遍歷對象的所有屬性,遞歸執行序列化方法,直到序列化的對象是String、Array或者是Eunm類,則按String、Array、Enum的序列化方式寫入字節流中。

3.1.2.2、ObjectInputStream源碼解析

ObjectInputStream的邏輯不用看也能猜到是和ObjectOutputStream相反的,反序列化的readObject方法具體邏輯如下:

 1  public final Object readObject()
 2             throws IOException, ClassNotFoundException
 3     {
 4         if (enableOverride) {
 5             return readObjectOverride();
 6         }
 7 
 8         // if nested read, passHandle contains handle of enclosing object
 9         int outerHandle = passHandle;
10         try {
11             //執行反序列化方法
12             Object obj = readObject0(false); 13             handles.markDependency(outerHandle, passHandle);
14             ClassNotFoundException ex = handles.lookupException(passHandle);
15             if (ex != null) {
16                 throw ex;
17             }
18             if (depth == 0) {
19                 vlist.doCallbacks();
20             }
21             return obj;
22         } finally {
23             passHandle = outerHandle;
24             if (closed && depth == 0) {
25                 clear();
26             }
27         }
28     }
 1 private Object readObject0(boolean unshared) throws IOException {
 2         boolean oldMode = bin.getBlockDataMode();
 3         if (oldMode) {
 4             int remain = bin.currentBlockRemaining();
 5             if (remain > 0) {
 6                 throw new OptionalDataException(remain);
 7             } else if (defaultDataEnd) {
 8                 /*
 9                  * Fix for 4360508: stream is currently at the end of a field
10                  * value block written via default serialization; since there
11                  * is no terminating TC_ENDBLOCKDATA tag, simulate
12                  * end-of-custom-data behavior explicitly.
13                  */
14                 throw new OptionalDataException(true);
15             }
16             bin.setBlockDataMode(false);
17         }
18 
19         byte tc;
20         //從流中讀取int數據,序列化時候寫入的時候寫入屬性之前都會寫入int值表示屬性的類型
21         while ((tc = bin.peekByte()) == TC_RESET) {
22             bin.readByte();
23             handleReset();
24         }
25 
26         depth++;
27         totalObjectRefs++;
28         try {
29             /**判斷讀取的int數據表示當前讀取的是什么類型的數據結構
30              * 不同的類型數據分別執行不同的解析方法*/
31             switch (tc) {
32                 case TC_NULL:
33                     return readNull();
34 
35                 case TC_REFERENCE:
36                     return readHandle(unshared);
37 
38                 case TC_CLASS:
39                     return readClass(unshared);
40 
41                 case TC_CLASSDESC:
42                 case TC_PROXYCLASSDESC:
43                     return readClassDesc(unshared);
44 
45                 case TC_STRING:
46                 case TC_LONGSTRING:
47                     return checkResolve(readString(unshared));
48 
49                 case TC_ARRAY:
50                     return checkResolve(readArray(unshared));
51 
52                 case TC_ENUM:
53                     return checkResolve(readEnum(unshared));
54 
55                 case TC_OBJECT:
56                     return checkResolve(readOrdinaryObject(unshared));
57 
58                 case TC_EXCEPTION:
59                     IOException ex = readFatalException();
60                     throw new WriteAbortedException("writing aborted", ex);
61 
62                 case TC_BLOCKDATA:
63                 case TC_BLOCKDATALONG:
64                     if (oldMode) {
65                         bin.setBlockDataMode(true);
66                         bin.peek();             // force header read
67                         throw new OptionalDataException(
68                                 bin.currentBlockRemaining());
69                     } else {
70                         throw new StreamCorruptedException(
71                                 "unexpected block data");
72                     }
73 
74                 case TC_ENDBLOCKDATA:
75                     if (oldMode) {
76                         throw new OptionalDataException(true);
77                     } else {
78                         throw new StreamCorruptedException(
79                                 "unexpected end of block data");
80                     }
81 
82                 default:
83                     throw new StreamCorruptedException(
84                             String.format("invalid type code: %02X", tc));
85             }
86         } finally {
87             depth--;
88             bin.setBlockDataMode(oldMode);
89         }
90     }

 

 1 private Object checkResolve(Object obj) throws IOException {
 2         if (!enableResolve || handles.lookupException(passHandle) != null) {
 3             return obj;
 4         }
 5         Object rep = resolveObject(obj);  6         if (rep != obj) {
 7             // The type of the original object has been filtered but resolveObject
 8             // may have replaced it;  filter the replacement's type
 9             if (rep != null) {
10                 if (rep.getClass().isArray()) {
11                     filterCheck(rep.getClass(), Array.getLength(rep));
12                 } else {
13                     filterCheck(rep.getClass(), -1);
14                 }
15             }
16             handles.setObject(passHandle, rep);
17         }
18         return rep;
19     }

 

解析的方法分別是readString()、readArray()、readEnum()、readOrdinaryObject()方法

解析字符串的方法就是直接從流中讀取字節,解析數組和枚舉的過程差不多,重點是解析對象的邏輯,代碼如下:

 1 private Object readOrdinaryObject(boolean unshared)
 2             throws IOException
 3     {
 4         //判斷是否是對象類型
 5         if (bin.readByte() != TC_OBJECT) {
 6             throw new InternalError();
 7         }
 8 
 9         //讀取類描述信息對象
10         ObjectStreamClass desc = readClassDesc(false);
11         desc.checkDeserialize();
12 
13         Class<?> cl = desc.forClass();
14         if (cl == String.class || cl == Class.class
15                 || cl == ObjectStreamClass.class) {
16             throw new InvalidClassException("invalid class descriptor");
17         }
18 
19         Object obj;
20         try {
21             /**
22              * isInstantiable方法是判斷是否有public的無參構造方法表示是否可以初始化對象
23              * 然后通過Constructor的newInstance方法初始化對象
24              * */
25             //通過反射執行Class的newInstance方法構造對象
26             obj = desc.isInstantiable() ? desc.newInstance() : null;
27         } catch (Exception ex) {
28             throw (IOException) new InvalidClassException(
29                     desc.forClass().getName(),
30                     "unable to create instance").initCause(ex);
31         }
32 
33         passHandle = handles.assign(unshared ? unsharedMarker : obj);
34         ClassNotFoundException resolveEx = desc.getResolveException();
35         if (resolveEx != null) {
36             handles.markException(passHandle, resolveEx);
37         }
38 
39         if (desc.isExternalizable()) {
40             //反序列化實現了Externalizable接口的對象
41  readExternalData((Externalizable) obj, desc); 42         } else {
43             //反序列化實現了Serializable接口的對象
44  readSerialData(obj, desc); 45         }
46 
47         handles.finish(passHandle);
48 
49         if (obj != null &&
50                 handles.lookupException(passHandle) == null &&
51                 desc.hasReadResolveMethod())
52         {
53             Object rep = desc.invokeReadResolve(obj);
54             if (unshared && rep.getClass().isArray()) {
55                 rep = cloneArray(rep);
56             }
57             if (rep != obj) {
58                 // Filter the replacement object
59                 if (rep != null) {
60                     if (rep.getClass().isArray()) {
61                         filterCheck(rep.getClass(), Array.getLength(rep));
62                     } else {
63                         filterCheck(rep.getClass(), -1);
64                     }
65                 }
66                 handles.setObject(passHandle, obj = rep);
67             }
68         }
69 
70         return obj;
71     }

 

和序列化的時候一樣,先判斷是實現了Externalizable接口還是Serializable接口分別執行不同的邏輯,readSerialData方法邏輯如下:

 1 private void readSerialData(Object obj, ObjectStreamClass desc)
 2             throws IOException
 3     {
 4         ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
 5         for (int i = 0; i < slots.length; i++) {//遍歷類描述信息
 6             ObjectStreamClass slotDesc = slots[i].desc;
 7             if (slots[i].hasData) {
 8                 if (obj == null || handles.lookupException(passHandle) != null) {
 9                     defaultReadFields(null, slotDesc); //沒有數據可以解析
10                 } else if (slotDesc.hasReadObjectMethod()) {//如果有自定義的readObejct方法則按自定義的邏輯執行
11                     ThreadDeath t = null;
12                     boolean reset = false;
13                     SerialCallbackContext oldContext = curContext;
14                     if (oldContext != null)
15                         oldContext.check();
16                     try {
17                         curContext = new SerialCallbackContext(obj, slotDesc);
18 
19                         bin.setBlockDataMode(true);
20                         slotDesc.invokeReadObject(obj, this);
21                     } catch (ClassNotFoundException ex) {
22                         /*
23                          * In most cases, the handle table has already
24                          * propagated a CNFException to passHandle at this
25                          * point; this mark call is included to address cases
26                          * where the custom readObject method has cons'ed and
27                          * thrown a new CNFException of its own.
28                          */
29                         handles.markException(passHandle, ex);
30                     } finally {
31                         do {
32                             try {
33                                 curContext.setUsed();
34                                 if (oldContext!= null)
35                                     oldContext.check();
36                                 curContext = oldContext;
37                                 reset = true;
38                             } catch (ThreadDeath x) {
39                                 t = x;  // defer until reset is true
40                             }
41                         } while (!reset);
42                         if (t != null)
43                             throw t;
44                     }
45 
46                     /*
47                      * defaultDataEnd may have been set indirectly by custom
48                      * readObject() method when calling defaultReadObject() or
49                      * readFields(); clear it to restore normal read behavior.
50                      */
51                     defaultDataEnd = false;
52                 } else {
53                     //沒有自定義的readObject方法則按默認的解析方法進行解析
54  defaultReadFields(obj, slotDesc); 55                 }
56 
57                 if (slotDesc.hasWriteObjectData()) {
58                     skipCustomData();
59                 } else {
60                     bin.setBlockDataMode(false);
61                 }
62             } else {
63                 if (obj != null &&
64                         slotDesc.hasReadObjectNoDataMethod() &&
65                         handles.lookupException(passHandle) == null)
66                 {
67                     slotDesc.invokeReadObjectNoData(obj);
68                 }
69             }
70         }
71

這里也是先判斷是否自定義了反序列化的方法readObject,如果有就按自定義的執行,如果沒有就執行默認的反序列化方法defaultReadFields方法執行

 1 private void defaultReadFields(Object obj, ObjectStreamClass desc)
 2             throws IOException
 3     {
 4         Class<?> cl = desc.forClass();
 5         if (cl != null && obj != null && !cl.isInstance(obj)) {
 6             throw new ClassCastException();
 7         }
 8 
 9         int primDataSize = desc.getPrimDataSize();
10         if (primVals == null || primVals.length < primDataSize) {
11             primVals = new byte[primDataSize];
12         }
13         bin.readFully(primVals, 0, primDataSize, false);
14         if (obj != null) {
15             //設置基本數據類型的屬性,只包括八大基本數據類型Integer、Long等不在內
16             desc.setPrimFieldValues(obj, primVals);
17         }
18 
19         int objHandle = passHandle;
20         ObjectStreamField[] fields = desc.getFields(false);
21         Object[] objVals = new Object[desc.getNumObjFields()];
22         int numPrimFields = fields.length - objVals.length;
23         for (int i = 0; i < objVals.length; i++) {
24             ObjectStreamField f = fields[numPrimFields + i];
25             objVals[i] = readObject0(f.isUnshared());//遞歸設置對象類型屬性
26             if (f.getField() != null) {
27                 handles.markDependency(objHandle, passHandle);
28             }
29         }
30         if (obj != null) {
31             desc.setObjFieldValues(obj, objVals);
32         }
33         passHandle = objHandle;
34     }

 

 基本上反序列化和序列化的整體邏輯是相反的

3.2、自定義序列化和反序列化

JDK除了支持默認的序列化邏輯,還可以自行定義序列化的方式,只需要對應的類中重寫writeObject方法和readObject方法即可,如下案例:

 1 /**自定義序列化方法*/
 2 private void writeObject(ObjectOutputStream outputStream)
 3             throws IOException
 4     {
 5         outputStream.writeLong(this.userId);
 6         outputStream.writeObject(this.userName);
 7         outputStream.writeObject(this.password);
 8     }
 9 
10 /**自定義反序列化方法*/
11 private void readObject(ObjectInputStream inputStream){
12         try {
13             this.userId = inputStream.readLong();
14             this.password = (String)inputStream.readObject();
15             this.userName = (String)inputStream.readObject();
16 //            this.addr = (String)inputStream.readObject();
17         } catch (Exception e) {
18             e.printStackTrace();
19         }
20     }

 

Tips:采用自定義的序列化方式有以下幾點必須滿足

1、writeObject和readObject方法必須同時重寫,如果只重寫一個,那么會拋異常,因為序列化之后的是字節序列,是嚴格按字節序列的順序來解析的

2、writeObject和readObject方法中的序列化和反序列化字段必須完全一致,也就是序列化字段的數量和順序(writeObejct可以多write幾個字段,但是順序必須在后面,readObject不可多字段,否則解析會拋異常)

3、writeObejct和readObject都必須是private修飾的,public修飾的不起作用

 

3.3、Externalizable接口實現序列化

除了Serializable接口可以實現序列化,實現了Externalizable接口同樣可以實現序列化,但是需要實現接口的序列化writeExternal和反序列化方法readExternal方法,如下示例:

1 public class User extends Person implements Externalizable

 

 1  public void writeExternal(ObjectOutput out) throws IOException {
 2         out.writeLong(this.userId);
 3         out.writeObject(this.password);
 4         out.writeObject(this.userName);
 5         out.writeObject(this.addr);
 6     }
 7 
 8     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
 9         this.userId = in.readLong();
10         this.userName = (String) in.readObject();
11         this.password= (String) in.readObject();//順序和序列化順序不一致,兩個字段的值會錯亂
12         this.addr = (String) in.readObject();
13     }

 

3.4、問題總結

3.4.1、序列化之前和反序列化之后的對象可能是同一個對象嗎?

可能,反序列化是通過對象的構造方法進行初始化的,所以正常情況下不會是之前的同一個對象,而且序列化之前的對象可能都已經被垃圾回收的;

但是在單例模式下是可以通過自定義反序列化邏輯進行操作的,詳情如下第二條。

3.4.2、序列化和反序列化破壞單例模式的解決?

 1 public class SingletonUser implements Serializable{
 2 
 3     private static SingletonUser user = new SingletonUser();
 4 
 5     public static SingletonUser getInstance(){
 6         return user;
 7     }
 8 
 9     public static void main(String[] args){
10         //1.通過單例類的靜態方法獲取單例
11         SingletonUser user = SingletonUser.getInstance();
12         try {
13             //2.序列化單例對象
14             serializer(user);
15             //3.反序列化獲取單例對象
16             SingletonUser user2 = derializer();
17             System.out.println(user);
18             System.out.println(user2);
19         } catch (Exception e) {
20             e.printStackTrace();
21         }
22     }
23 
24     /**序列化對象*/
25     private static void serializer(SingletonUser user)throws Exception{
26         ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/xingwuxu/testlog/user.txt")));
27         outputStream.writeObject(user);
28         outputStream.close();
29     }
30 
31     /**反序列化對象*/
32     private static SingletonUser derializer()throws Exception{
33         ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("/Users/xingwuxu/testlog/user.txt")));
34         SingletonUser user = (SingletonUser) inputStream.readObject();
35         inputStream.close();
36         return user;
37     }
38 }

 測試結果為:

1 com.lucky.demo.base.seralizer.demo.SingletonUser@266474c2
2 com.lucky.demo.base.seralizer.demo.SingletonUser@2f4d3709

 

不出所料,通過序列化之后再反序列化之后的對象不是同一個對象,但是這就破壞了單例模式,因為單例模式就是應該只能創建一個對象的,但是通過序列化操作之后對象就會可以被創建多個。所以此時就需要通過自定義序列化的方式來解決破壞單例模式。

解決辦法如下:在單例類中定義方法readResolve方法,並且返回Object對象

 private Object readResolve(){
        return user;
    }

 

 測試結果如下:

com.lucky.demo.base.seralizer.demo.SingletonUser@266474c2
com.lucky.demo.base.seralizer.demo.SingletonUser@266474c2

 

但是如果返回返回的不是Object對象,而是SingletonUser對象,則會無效,如下:

private SingletonUser readResolve(){
        return user;
    }

 

測試結果為:

com.lucky.demo.base.seralizer.demo.SingletonUser@266474c2
com.lucky.demo.base.seralizer.demo.SingletonUser@2f4d3709

 

接下來就分析下原理是什么,在反序列化關鍵類ObjectInputStream類中的readOrdinaryObject方法中在反序列化之后有如下邏輯:

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())//判斷是否含有readResolve()方法
    {
        Object rep = desc.invokeReadResolve(obj);//通過反射的方式執行對象的readResolve方法
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            // Filter the replacement object
            if (rep != null) {
                if (rep.getClass().isArray()) {
                    filterCheck(rep.getClass(), Array.getLength(rep));
                } else {
                    filterCheck(rep.getClass(), -1);
                }
            }
            handles.setObject(passHandle, obj = rep);//將通過readResolve創建的對象賦值給obj對象
        }
    }

 

先判斷該類中是否包含有readResolveMethod方法,如果有就通過readResolve方法返回結果,再將返回的結果賦值給通過反序列化生成的對象,所以只需要readResolve方法返回的也是同樣的單例對象即可。

 

3.4.3、實現了Serializable接口的serialVersionUID作用是什么?

在反序列化時,JVM需要知道所屬的class文件,在序列化的時候JVM會記錄class文件的版本號,默認是JVM自動生成,也可以手動定義。反序列化時JVM會按版本號找指定版本的class文件進行反序列化,

如果class文件有版本號在序列化和反序列化時不一致就會導致反序列化失敗,會拋異常提示版本號不一致,如:

1 Exception in thread "main" java.io.InvalidClassException: com.lucky.demo.base.seralizer.demo.User; local class incompatible: stream classdesc serialVersionUID = 512, local class serialVersionUID = 2276725928587165175
2     at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
3     at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
4     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
5     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
6     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
7     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
8     at com.lucky.demo.base.seralizer.demo.MainTest.derializer(MainTest.java:68)
9     at com.lucky.demo.base.seralizer.demo.MainTest.main(MainTest.java:47)

 

3.4.4、JDK序列化的整體邏輯流程?

序列化流程:

1、調用ObjectStreamClass.lookup方法得到類的描述對象(保護類的基本數據屬性、對象屬性、是否包含readObject、writeObject等和序列化相關的方法等)

2、判斷對象的類型如果是String、Array、Eunm則直接轉化為字節序列,如果是對象類型(實現了Serializable接口的屬性)則執行writeOrdinaryObject方法

3、如果屬性所屬的類自定義了writeObject方法則通過反射的方式invoke自定義的writeObject方法,如果沒有自定義則按默認的序列化方法defaultWriteFields方法

4、遞歸所有屬性,如果是基本數據類型則直接寫入流中,如果不是基本數據類型則遞歸屬性繼續執行writeObject方法寫入流中

5、最終的結果是將對象轉化成二進制流中,所以需要依次按順序將對象的信息寫入到流中

 

反序列化流程:

1、從字節流中讀取字節,判斷字節對應所屬的類型(Class、String、Object等等)

2、根據不同的類型,繼續讀取對應的字節數據,如果不是對象類型那么就可以直接解析出基本數據類型的數據

3、如果是對象類型,先讀取字節TC_CLASSDESC,從而解析讀取並解析出ObjectStreamClass對象,也就是類的描述信息對象

4、通過ObjectStreamClass可以得到類的構造方法,並通過構造器的newInstance初始化對象,然后開始給屬性賦值

5、判斷是否有自定義readObject,有的話通過反射執行readObject方法反序列化,如果沒有則按默認的反序列化方法defaultReadFields方法

6、依次從流中讀取屬性的值進行賦值,如果是對象類型則遞歸繼續執行readObject方法進行讀取流操作

 

3.4.5、JDK序列化都保存了對象的哪些內容?

對象的屬性、父類的可序列化屬性、類的描述信息(類名稱、字段、構造方法、自定義序列化相關方法等)

 


 

除了JDK自帶的序列化方式,還有其他的幾種序列化方式分別整理如下:

 JAVA基礎4---序列化和反序列化深入整理(JSON序列化)

JAVA基礎4---序列化和反序列化深入整理(Hessian序列化)

JAVA基礎4---序列化和反序列化深入整理(Kryo序列化)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM