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

简介: When---什么时候需要序列化和反序列化: 简单的写一个hello world程序,用不到序列化和反序列化。写一个排序算法也用不到序列化和反序列化。但是当你想要将一个对象进行持久化写入文件,或者你想将一个对象从一个网络地址通过网络协议发送到另一个网络地址时,这时候就需要考虑序列化和反序列化了。

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方法,返回单例。防止通过序列化和反序列化导致单例模式生效的问题。

黎明前最黑暗,成功前最绝望!
相关文章
|
16天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【4月更文挑战第3天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin的兴起,其在Android开发中的地位逐渐上升,但关于其与Java在性能方面的对比,尚无明确共识。本文通过深入分析并结合实际测试数据,探讨了Kotlin与Java在Android平台上的性能表现,揭示了在不同场景下两者的差异及其对应用性能的潜在影响,为开发者在选择编程语言时提供参考依据。
|
29天前
|
存储 C#
C#中的序列化和反序列化
C#中的序列化和反序列化
12 0
|
29天前
|
Java 编译器 Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【2月更文挑战第30天】 随着Kotlin成为开发Android应用的首选语言,开发者社区对于其性能表现持续关注。本文通过深入分析与基准测试,探讨Kotlin与Java在Android平台上的性能差异,揭示两种语言在编译效率、运行时性能和内存消耗方面的具体表现,并提供优化建议。我们的目标是为Android开发者提供科学依据,帮助他们在项目实践中做出明智的编程语言选择。
|
1月前
|
安全 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【2月更文挑战第24天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin在Android开发中的普及,了解其与Java在性能方面的差异变得尤为重要。本文通过深入分析和对比两种语言的运行效率、启动时间、内存消耗等关键指标,揭示了Kotlin在实际项目中可能带来的性能影响,并提供了针对性的优化建议。
27 0
|
23天前
|
Java 编译器 Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
在开发高性能的Android应用时,选择合适的编程语言至关重要。近年来,Kotlin因其简洁性和功能性受到开发者的青睐,但其性能是否与传统的Java相比有所不足?本文通过对比分析Kotlin与Java在Android平台上的运行效率,揭示二者在编译速度、运行时性能及资源消耗方面的具体差异,并探讨在实际项目中如何做出最佳选择。
17 4
|
1月前
|
Java 编译器 Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【2月更文挑战第24天】 在移动开发领域,性能优化一直是开发者关注的重点。随着Kotlin的兴起,许多Android开发者开始从传统的Java转向Kotlin进行应用开发。本文将深入探讨Kotlin与Java在Android平台上的性能表现,通过对比分析两者在编译效率、运行时性能和内存消耗等方面的差异。我们将基于实际案例研究,为开发者提供选择合适开发语言的数据支持,并分享一些提升应用性能的最佳实践。
|
6天前
|
存储 Java
Java输入输出:解释一下序列化和反序列化。
Java中的序列化和反序列化是将对象转换为字节流和反之的过程。ObjectOutputStream用于序列化,ObjectInputStream则用于反序列化。示例展示了如何创建一个实现Serializable接口的Person类,并将其序列化到文件,然后从文件反序列化回Person对象。
15 5
|
24天前
|
算法 安全 Java
Java中的并发编程优化探究
本文深入探讨了Java中的并发编程优化策略,从锁机制到线程池的使用,以及常见的并发问题与解决方案。通过分析不同的优化方法,帮助开发者更好地应对并发环境下的挑战,提高程序性能和稳定性。
11 0
|
27天前
|
安全 Java 开发者
Java并发编程中的线程安全性探究
在Java编程中,线程安全性是一个至关重要的问题,涉及到多线程并发访问共享资源时可能出现的数据竞争和不一致性问题。本文将深入探讨Java并发编程中的线程安全性,介绍常见的线程安全性问题以及解决方法,帮助开发者更好地理解和应对在多线程环境下的挑战。
|
27天前
|
安全 Java 开发者
Java并发编程中的线程安全性探究
在Java开发中,多线程编程是一项常见且重要的技术。本文将深入探讨Java并发编程中的线程安全性问题,从数据竞争到同步机制的选择,帮助开发者更好地理解和应对多线程环境下的挑战。
13 2

热门文章

最新文章