java序列化反序列化深入探究


When---什么時候需要序列化和反序列化:

簡單的寫一個hello world程序,用不到序列化和反序列化。寫一個排序算法也用不到序列化和反序列化。但是當你想要將一個對象進行持久化寫入文件,或者你想將一個對象從一個網絡地址通過網絡協議發送到另一個網絡地址時,這時候就需要考慮序列化和反序列化了。另外如果你想對一個對象實例進行深度拷貝,也可以通過序列化和反序列化的方式進行。

 

What---什么是序列化和反序列化:

Serialization-序列化:可以看做是將一個對象轉化為二進制流的過程

Deserialization-反序列化:可以看做是將對象的二進制流重新讀取轉換成對象的過程

 

How---怎么實現序列化:

只有實現了 Serializable 或 Externalizable 接口的類的對象才能被序列化,否則拋出異常。
對於實現了這兩個接口,具體序列化和反序列化的過程又分以下3中情況:
情況1:若類僅僅實現了Serializable接口,則可以按照以下方式進行序列化和反序列化
ObjectOutputStream采用默認的序列化方式,對對象的非transient的實例變量進行序列化。
ObjcetInputStream采用默認的反序列化方式,對對象的非transient的實例變量進行反序列化。

情況2:若類不僅實現了Serializable接口,並且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),則采用以下方式進行序列化與反序列化。
ObjectOutputStream調用對象的writeObject(ObjectOutputStream out)的方法進行序列化。
ObjectInputStream會調用對象的readObject(ObjectInputStream in)的方法進行反序列化。

情況3:若類實現了Externalnalizable接口,且類必須實現readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,則按照以下方式進行序列化與反序列化。
ObjectOutputStream調用對象的writeExternal(ObjectOutput out))的方法進行序列化。
ObjectInputStream會調用對象的readExternal(ObjectInput in)的方法進行反序列化。

 為了進一步說明,我們直接看jdk底層ArrayList的序列化和反序列化:

 1 // 實現了Serializable接口,可以被序列化
 2 public class ArrayList<E> extends AbstractList<E>
 3         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
 4 {
 5     private static final long serialVersionUID = 8683452581122892189L;
 6 
 7     /**
 8      * The array buffer into which the elements of the ArrayList are stored.
 9      * The capacity of the ArrayList is the length of this array buffer.
10      */
11     // 實際元素被transient修飾,默認不會進行序列化
12     private transient Object[] elementData;
13 
14     .....
15 
16     /**
17      * Save the state of the <tt>ArrayList</tt> instance to a stream (that
18      * is, serialize it).
19      *
20      * @serialData The length of the array backing the <tt>ArrayList</tt>
21      *             instance is emitted (int), followed by all of its elements
22      *             (each an <tt>Object</tt>) in the proper order.
23      */
24     private void writeObject(java.io.ObjectOutputStream s)
25         throws java.io.IOException{
26     // Write out element count, and any hidden stuff
27     int expectedModCount = modCount;
28     s.defaultWriteObject();
29 
30         // Write out array length
31         s.writeInt(elementData.length);
32 
33     // Write out all elements in the proper order.
34     for (int i=0; i<size; i++)
35             s.writeObject(elementData[i]);
36 
37     if (modCount != expectedModCount) {
38             throw new ConcurrentModificationException();
39         }
40 
41     }
42     
43     /**
44      * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
45      * deserialize it).
46      */
47     private void readObject(java.io.ObjectInputStream s)
48         throws java.io.IOException, ClassNotFoundException {
49     // Read in size, and any hidden stuff
50     s.defaultReadObject();
51 
52         // Read in array length and allocate array
53         int arrayLength = s.readInt();
54         Object[] a = elementData = new Object[arrayLength];
55 
56     // Read in all elements in the proper order.
57     for (int i=0; i<size; i++)
58             a[i] = s.readObject();
59     }
60 }

可以看到,初看之下ArrayList的實際存儲元素不能被序列化。但實際上根據我們上面的第二條原則,知道因為其重寫了writeObject和readObject方法,而在方法的內部實現了對具體存儲對象的序列化與反序列化。那么這兩個方法究竟是在什么時候執行的呢?我們需要轉到ObjectOutputStream這個對象上來:

  1 /**
  2  * Serialization's descriptor for classes.  It contains the name and
  3  * serialVersionUID of the class.  The ObjectStreamClass for a specific class
  4  * loaded in this Java VM can be found/created using the lookup method. 16  */
 17 // 在序列化對象之前會封裝一個ObjectStreamClass對象
 18 public class ObjectStreamClass implements Serializable  {
 19     /** class-defined writeObject method, or null if none */
 20     private Method writeObjectMethod;
 21     
 22      /**
 23      * Creates local class descriptor representing given class.
 24      */
 25     private ObjectStreamClass(final Class cl) { 36    
      ......
37 if (serializable) { 38 AccessController.doPrivileged(new PrivilegedAction() { 39 public Object run() { 40 if (isEnum) { 41 suid = Long.valueOf(0); 42 fields = NO_FIELDS; 43 return null; 44 } 45 if (cl.isArray()) { 46 fields = NO_FIELDS; 47 return null; 48 } 49 50 suid = getDeclaredSUID(cl); 51 try { 52 fields = getSerialFields(cl); 53 computeFieldOffsets(); 54 } catch (InvalidClassException e) { 55 serializeEx = deserializeEx = e; 56 fields = NO_FIELDS; 57 } 58 59 if (externalizable) { 60 cons = getExternalizableConstructor(cl); 61 } else { 62 cons = getSerializableConstructor(cl); 63 // 其實就是writeObject方法 64 writeObjectMethod = getPrivateMethod(cl, "writeObject", 65 new Class[] { ObjectOutputStream.class }, 66 Void.TYPE); 67 readObjectMethod = getPrivateMethod(cl, "readObject", 68 new Class[] { ObjectInputStream.class }, 69 Void.TYPE); 70 readObjectNoDataMethod = getPrivateMethod( 71 cl, "readObjectNoData", null, Void.TYPE); 72 hasWriteObjectData = (writeObjectMethod != null); 73 } 74 writeReplaceMethod = getInheritableMethod( 75 cl, "writeReplace", null, Object.class); 76 readResolveMethod = getInheritableMethod( 77 cl, "readResolve", null, Object.class); 78 return null; 79 } 80 }); 81 } else { 82 suid = Long.valueOf(0); 83 fields = NO_FIELDS; 84 } 85 86   .......107 } 108 109 /** 110 * Returns non-static private method with given signature defined by given 111 * class, or null if none found. Access checks are disabled on the 112 * returned method (if any). 113 */ 114 private static Method getPrivateMethod(Class cl, String name, 115 Class[] argTypes, 116 Class returnType) 117 { 118 try { 119 Method meth = cl.getDeclaredMethod(name, argTypes); 120 meth.setAccessible(true); 121 int mods = meth.getModifiers(); 122 return ((meth.getReturnType() == returnType) && 123 ((mods & Modifier.STATIC) == 0) && 124 ((mods & Modifier.PRIVATE) != 0)) ? meth : null; 125 } catch (NoSuchMethodException ex) { 126 return null; 127 } 128 } 129 130 131 /** 132 * Returns true if represented class is serializable (but not 133 * externalizable) and defines a conformant writeObject method. Otherwise, 134 * returns false. 135 */ 136 boolean hasWriteObjectMethod() { 137 return (writeObjectMethod != null); 138 } 139 } 140 141 public class ObjectOutputStream 142 extends OutputStream implements ObjectOutput, ObjectStreamConstants 143 { 144 /** 145 * Magic number that is written to the stream header. 146 */ 147 final static short STREAM_MAGIC = (short)0xaced; 148 149 /** 150 * Version number that is written to the stream header. 151 */ 152 final static short STREAM_VERSION = 5; 153 154 155 public ObjectOutputStream(OutputStream out) throws IOException { 156 verifySubclass(); 157 bout = new BlockDataOutputStream(out); 158 handles = new HandleTable(10, (float) 3.00); 159 subs = new ReplaceTable(10, (float) 3.00); 160 enableOverride = false; 161 // 寫入頭信息 162 writeStreamHeader(); 163 bout.setBlockDataMode(true); 164 if (extendedDebugInfo) { 165 debugInfoStack = new DebugTraceInfoStack(); 166 } else { 167 debugInfoStack = null; 168 } 169 } 170 171 protected void writeStreamHeader() throws IOException { 172 bout.writeShort(STREAM_MAGIC); 173 bout.writeShort(STREAM_VERSION); 174 } 175 176 /** 177 * Write the specified object to the ObjectOutputStream. The class of the 178 * object, the signature of the class, and the values of the non-transient 179 * and non-static fields of the class and all of its supertypes are 180 * written. Default serialization for a class can be overridden using the 181 * writeObject and the readObject methods. Objects referenced by this 182 * object are written transitively so that a complete equivalent graph of 183 * objects can be reconstructed by an ObjectInputStream.196 */ 197 public final void writeObject(Object obj) throws IOException { 198 if (enableOverride) { 199 writeObjectOverride(obj); 200 return; 201 } 202 try { 203 writeObject0(obj, false); 204 } catch (IOException ex) { 205 if (depth == 0) { 206 writeFatalException(ex); 207 } 208 throw ex; 209 } 210 } 211 212 /** 213 * Underlying writeObject/writeUnshared implementation. 214 */ 215 private void writeObject0(Object obj, boolean unshared) 216 throws IOException 217 { 218 boolean oldMode = bout.setBlockDataMode(false); 219 depth++; 220 try { 221 // handle previously written and non-replaceable objects 222   ...... 237 // check for replacement object 238 ......241 261 262 // if object replaced, run through original checks a second time 263   ......279 280 // remaining cases 281 if (obj instanceof String) { 282 writeString((String) obj, unshared); 283 } else if (cl.isArray()) { 284 writeArray(obj, desc, unshared); 285 } else if (obj instanceof Enum) { 286 writeEnum((Enum) obj, desc, unshared); 287 } else if (obj instanceof Serializable) { 288 // 如果不是特殊對象類型,最終會調用該方法 289 writeOrdinaryObject(obj, desc, unshared); 290 } else { 291 if (extendedDebugInfo) { 292 throw new NotSerializableException( 293 cl.getName() + "\n" + debugInfoStack.toString()); 294 } else { 295 throw new NotSerializableException(cl.getName()); 296 } 297 } 298 } finally { 299 depth--; 300 bout.setBlockDataMode(oldMode); 301 } 302 } 303 304 private void writeOrdinaryObject(Object obj, 305 ObjectStreamClass desc, 306 boolean unshared) 307 throws IOException 308 { 309 if (extendedDebugInfo) { 310 debugInfoStack.push( 311 (depth == 1 ? "root " : "") + "object (class \"" + 312 obj.getClass().getName() + "\", " + obj.toString() + ")"); 313 } 314 try { 315 desc.checkSerialize(); 316 317 bout.writeByte(TC_OBJECT); 318 writeClassDesc(desc, false); 319 handles.assign(unshared ? null : obj); 320 if (desc.isExternalizable() && !desc.isProxy()) { 321 writeExternalData((Externalizable) obj); 322 } else { 323 // 一般情況下會調用該方法 324 writeSerialData(obj, desc); 325 } 326 } finally { 327 if (extendedDebugInfo) { 328 debugInfoStack.pop(); 329 } 330 } 331 } 332 333   /** 334 * Writes instance data for each serializable class of given object, from 335 * superclass to subclass. 336 */ 337 private void writeSerialData(Object obj, ObjectStreamClass desc) 338 throws IOException 339 { 340 ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); 341 for (int i = 0; i < slots.length; i++) { 342 ObjectStreamClass slotDesc = slots[i].desc; 343 // 如果重寫了序列化的方法writeObject,則調用對應的方法進行寫入,其實就是ObjectStreamClass 中的對應方法,可以得出序列化的第2條規則 344 if (slotDesc.hasWriteObjectMethod()) { 345 PutFieldImpl oldPut = curPut; 346 curPut = null; 347 348 if (extendedDebugInfo) { 349 debugInfoStack.push( 350 "custom writeObject data (class \"" + 351 slotDesc.getName() + "\")"); 352 } 353 354 SerialCallbackContext oldContext = curContext; 355 try { 356 curContext = new SerialCallbackContext(obj, slotDesc); 357 358 bout.setBlockDataMode(true); 359 slotDesc.invokeWriteObject(obj, this); 360 bout.setBlockDataMode(false); 361 bout.writeByte(TC_ENDBLOCKDATA); 362 } finally { 363 curContext.setUsed(); 364 curContext = oldContext; 365 366 if (extendedDebugInfo) { 367 debugInfoStack.pop(); 368 } 369 } 370 371 curPut = oldPut; 372 } else { 373 // 未重寫調用默認的方法 374 defaultWriteFields(obj, slotDesc); 375 } 376 } 377 }

以上代碼就是分析序列化情況2的實現,反序列化也可以同樣跟蹤發現,這里不再重復。

 

Deeper---其他序列化反序列化的深入問題:

a. 被transient和static修飾的成員變量不會被序列化

b. 有個需要注意的點來自 Serializable 接口的說明文檔,簡單說明如下:

假設A實現了Serializable接口,且A為B的子類,B沒有實現Serializable接口。那么在序列化和反序列話的時候,B的無參構造函數負責B的相關屬性的序列化和反序列化。特殊的,當B沒有無參構造函數的時候,將A對象進行序列化時不會報錯,但是反序列化獲取A的時候報錯。

 1 static class SubSerializableTest extends SerializableTest implements Serializable {
 2         private static final long serialVersionUID = 1L;
 3         
 4         private String subName;
 5         
 6         public SubSerializableTest(String name, String subName) {
 7             super(name, 18);
 8             this.subName = subName;
 9         }
10 
11         public String getSubName() {
12             return subName;
13         }
14     }
15     
16     static class SerializableTest {
17         
18         public SerializableTest() {
19             this.name = "aaa";
20             this.age = 21;
21         }
22         
23         public SerializableTest(String name, int age) {
24             this.name = name;
25         }
26         
27         private String name;
28         
29         private int age;
30 
31         public String getName() {
32             return name;
33         }
34 
35         public void setName(String name) {
36             this.name = name;
37         }
38 
39         public int getAge() {
40             return age;
41         }
42 
43         public void setAge(int age) {
44             this.age = age;
45         }
46     }
47     
48     SubSerializableTest subTest = new SubSerializableTest("KiDe", "KiDe");
49     ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("e:/1.txt")));
50     oos.writeObject(subTest);
51     oos.close();
52     
53     ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("e:/1.txt")));
54     subTest = (SubSerializableTest) ois.readObject();        // 如果SerializableTest未實現無參的構造函數,則拋出 Exception in thread "main" java.io.InvalidClassException: test.Test$SubSerializableTest; test.Test$SubSerializableTest; no valid constructor
55     System.out.println(subTest.getName());        // aaa
56     System.out.println(subTest.getSubName());    // KiDe
57     ois.close();

另外多說一句,假設B是A的一個屬性但是B沒有實現 Serializable 接口,這時候不管序列化還是反序列化A都會報異常:
Exception in thread "main" java.io.NotSerializableException: test.Test$SerializableTest。

c. 由於上面所講的限制,就存在需要特殊處理未實現 Serializable 接口的屬性,這時候可以重寫下面三個方法:
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;


前面兩個方法主要用來序列化和反序列化被transient或者static修飾的屬性,將其寫入流:

 1 private void writeObject(ObjectOutputStream out) throws IOException {
 2 System.out.println("writeOject");
 3     out.defaultWriteObject();
 4     out.writeInt(123);
 5 }
 6 
 7 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
 8     System.out.println("readOject");
 9     in.defaultReadObject();
10     System.out.println(in.readInt());;
11 }

第三個方法屬於一種防御性方法,一般不會用到,官方解釋是;
The readObjectNoData method is responsible for initializing the state of the object for its particular class in the event that the serialization stream does not list the given class as a superclass of the object being deserialized. This may occur in cases where the receiving party uses a different version of the deserialized instance's class than the sending party, and the receiver's version extends classes that are not extended by the sender's version. This may also occur if the serialization stream has been tampered; hence, readObjectNoData is useful for initializing deserialized objects properly despite a "hostile" or incomplete source stream.

這里暫時沒有試驗出這個方法的使用場景,略過。

 

d.  還有兩個方法在序列化和反序列化的時候會被自動調用到:

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

其中writeReplace調用在writeObject之前,可以修改對象屬性,最終返回this,readResolve調用在readObject之后,可以修改讀取到的對象的屬性,返回this

一般的應用是在單例模式中,重寫readResoive方法,返回單例。防止通過序列化和反序列化導致單例模式生效的問題。


免責聲明!

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



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