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

时间: 2024-07-29 20:30:02

java序列化反序列化深入探究的相关文章

java序列化/反序列化之xml、protobuf、protostuff 的比较与使用例子

目录 1.背景 2.测试 2.1.环境 2.2.工具 2.3.说明 2.4.结果 2.5.结论 3.xml简单教程 3.1.准备 3.2.代码 4.protobuf简单教程 4.1.快速入门 1.下载.exe编译器 2.编写.proto文件 3.利用编译器编译.proto文件生成javabean 4.引用jar包 5.直接使用javabean自带的序列化.反序列化.提取属性等方法 5.protostuff简单教程 5.1.快速入门 1.引用jar包 2.直接使用相关序列化.反序列化语法 1.背景

java序列化之protobuf

package com.book.core.test; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import com.book.core.model.Type; import com.book.core.serializable.SerializationUtil; import com.dyuproject.protostuff.LinkedBuffer; import co

Java 序列化与反序列化

1.什么是序列化?为什么要序列化? Java 序列化就是指将对象转换为字节序列的过程,而反序列化则是只将字节序列转换成目标对象的过程. 我们都知道,在进行浏览器访问的时候,我们看到的文本.图片.音频.视频等都是通过二进制序列进行传输的,那么如果我们需要将Java对象进行传输的时候,是不是也应该先将对象进行序列化?答案是肯定的,我们需要先将Java对象进行序列化,然后通过网络,IO进行传输,当到达目的地之后,再进行反序列化获取到我们想要的对象,最后完成通信. 2.如何实现序列化 2.1.使用到JD

Java序列化1:序列化、反序列化和transient关键字的作用

网上讲Java序列化的文章很多,感觉很多都讲得不全,这篇文章希望可以全面地剖析Java的序列化机制.为什么要进行序列化和反序列化?我们写了一个Object,但那是Java虚拟机堆内存里面的东西,利用Object进行网络通信.IO操作的时候怎么会认识Java堆内存里面的东西?所以,需要序列化和反序列化机制的保障. 序列化:将一个对象转换成一串二进制表示的字节数组,通过保存或转移这些字节数据来达到持久化的目的. 反序列化:将字节数组重新构造成对象. 默认序列化 序列化只需要实现java.io.Ser

java序列化和反序列化

一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中: 2) 在网络上传送对象的字节序列. 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些s

java序列化与反序列化以及浅谈一下hadoop的序列化

1.什么是序列化和反序列化 神马是序列化呢,序列化就是把内存中的对象的状态信息,转换成字节序列以便于存储(持久化)和网络传输.(网络传输和硬盘持久化,你没有一定的手段来进行辨别这些字节序列是什么东西,有什么信息,这些字节序列就是垃圾). 反序列化就是将收到字节序列或者是硬盘的持久化数据,转换成内存中的对象. 2.JDK的序列化 JDK的序列化只有实现了serializable接口就能实现序列化与反序列化,但是记得一定要加上序列化版本ID serialVersionUID 这个是识别序列化的之前那

10.8-全栈Java笔记:序列化/反序列化的步骤和实例

本节我们详细讲解10.3节中提到的序列化和反序列化操作. 序列化和反序列化是什么 当两个进程远程通信时,彼此可以发送各种类型的数据. 无论是何种类型的数据,都会以二进制序列的形式在网络上传送.比如,我们可以通过http协议发送字符串信息:我们也可以在网络上直接发送JAVA对象.发送方需要把这个Java对象转换为字节序列,才能在网络上传送:接收方则需要把字节序列再恢复为Java对象. 把Java对象转换为字节序列的过程称为对象的序列化.把字节序列恢复为Java对象的过程称为对象的反序列化. 对象序

Java序列化与反序列化学习(三):序列化机制与原理

Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的 过程.Java序列化API提供一种处理对象序列化的标准机制.在这里你能学到如何序列化一个对象,什么时候需要序列化以及Java序列化的算法,我们用 一个实例来示范序列化以后的字节是如何描述一个对象的信息的. 序列化的必要性 Java中,一切都是对象,在分布式环境中经常需要将Object从这一端网络或设备传递到另一端.这就需要有一种

Java序列化与反序列化(实践)

Java序列化与反序列化(实践) 基本概念:序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储和传输数据. 昨天在一本书上看到了,好好实践了一下,序列化为一般文件,也序列化为XML文件(使用XStream). 用于序列化的实体类Person.java    代码如下(记得需要实现Serializable接口):import java.io.Serializable; @SuppressWarnings("serial&