关于序列化的用法及基础知识,因为不是本文重点,这里再也不详细介绍了,这里只作简单的介绍。java
java.io.Serializable
接口,那么它就能够被序列化。ObjectOutputStream
和ObjectInputStream
对对象进行序列化及反序列化。private static final long serialVersionUID
)Serializable
接口。为了深刻的介绍序列化,咱们这篇文章准备从Java源码中的ArrayList类入手。看看Java自身是如何使用序列化的。在介绍ArrayList序列化以前,先来考虑一个问题:数组
问:如何自定义的序列化和反序列化策略?安全
带着这个问题,咱们来看java.util.ArrayList
的源码服务器
code1:dom
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; transient Object[] elementData; // non-private to simplify nested class access private int size; }
笔者省略了其余成员变量,从上面的代码中能够知道ArrayList实现了java.io.Serializable
接口,那么咱们就能够对它进行序列化及反序列化。由于负责保存元素的elementData是transient
的,因此咱们认为这个成员变量的内容不会被序列化而保留下来。咱们写一个Demo,验证一下咱们的想法: 优化
code2:加密
public static void main(String[] args) throws IOException, ClassNotFoundException { List<String> stringList = new ArrayList<String>(); stringList.add("hello"); stringList.add("world"); stringList.add("hollis"); stringList.add("chuang"); System.out.println("init StringList" + stringList); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist")); objectOutputStream.writeObject(stringList); IOUtils.close(objectOutputStream); File file = new File("stringlist"); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file)); List<String> newStringList = (List<String>)objectInputStream.readObject(); IOUtils.close(objectInputStream); if(file.exists()){ file.delete(); } System.out.println("new StringList" + newStringList); } //init StringList[hello, world, hollis, chuang] //new StringList[hello, world, hollis, chuang]
了解ArrayList的人都知道,ArrayList底层是经过数组实现的。那么数组elementData
其实就是用来保存列表中的元素的。经过该属性的声明方式,咱们认为,他应该是没法经过序列化持久化下来的。spa
问:为何code 2的结果却经过序列化和反序列化把List中的元素保留下来了呢?debug
在ArrayList中定义了来个方法: writeObject
和readObject
。这里先给出结论:code
在序列化过程当中,若是被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。若是没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
也就述说,用户自定义的 writeObject 和 readObject 方法能够容许用户控制序列化的过程,好比能够在序列化的过程当中动态改变序列化的数值。咱们发现ArrayList中有这两个方法的实现,那么基本能够肯定,elementData能被序列化持久下来,确定和这两个方法有关,虽然他被声明为transitent,那么咱们来看一下ArrayList类中这两个方法的具体实现:
code 3:
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }
code4:
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
经过上面两段代码,咱们发现,raedObject方法和writeObjec方法中定义了关于elementData的序列化策略。如今,咱们能够回答刚刚的问题了。
问:为何code 2的结果却经过序列化和反序列化把List中的元素保留下来了呢?
答:ArrayList中定义了raedObject和writeObject方法,这两个方法中定义了elementData的序列化及反序列化策略。
那么,问题又来了。
问:为何ArrayList要用这种方式来实现序列化呢?
why transient
ArrayList其实是动态数组,每次在放满之后自动增加设定的长度值,若是数组自动增加长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。
why writeObject and readObject
前面说过,为了防止一个包含大量空对象的数组被序列化,为了优化存储,因此,ArrayList使用transient
来声明elementData
。 可是,做为一个集合,在序列化过程当中还必须保证其中的元素能够被持久化下来,因此,经过重写writeObject
和 readObject
方法的方式把其中的元素保留下来。
writeObject
方法把elementData
数组中的元素遍历的保存到输出流(ObjectOutputStream)中。readObject
方法从输入流(ObjectInputStream)中读出对象并保存赋值到elementData
数组中。
至此,咱们先试着来回答刚刚提出的问题:
问:为何ArrayList要用这种方式来实现序列化呢?
答:避免elementData数组中过多的无用的null被序列化。
问:如何自定义的序列化和反序列化策略?
答:能够经过在被序列化的类中增长writeObject 和 readObject方法。
那么问题又来了,虽然ArrayList中写了writeObject 和 readObject 方法,可是这两个方法并无显示的被调用啊。
问:若是一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的呢?
从code 4中,咱们能够看出,对象的序列化过程经过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,咱们来分析一下ArrayList中的writeObject 和 readObject 方法究竟是如何被调用的呢?
为了节省篇幅,这里给出ObjectOutputStream的writeObject的调用栈:
writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject
这里看一下invokeWriteObject:
void invokeWriteObject(Object obj, ObjectOutputStream out) throws IOException, UnsupportedOperationException { if (writeObjectMethod != null) { try { writeObjectMethod.invoke(obj, new Object[]{ out }); } catch (InvocationTargetException ex) { Throwable th = ex.getTargetException(); if (th instanceof IOException) { throw (IOException) th; } else { throwMiscException(th); } } catch (IllegalAccessException ex) { // should not occur, as access checks have been suppressed throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } }
其中writeObjectMethod.invoke();
是关键,经过反射的方式调用writeObjectMethod方法。官方是这么解释这个writeObjectMethod的:
class-defined writeObject method, or null if none
在咱们的例子中,这个方法就是咱们在ArrayList中定义的writeObject方法。经过反射的方式被调用了。
至此,咱们先试着来回答刚刚提出的问题:
问:若是一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的?
答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时,会经过反射的方式调用。
至此,咱们已经介绍完了ArrayList的序列化方式。那么,不知道有没有人提出这样的疑问:
问:Serializable明明就是一个空的接口,它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢?
Serializable接口的定义:
public interface Serializable { }
若是尝试对一个未实现Serializable接口的类进行序列化,会抛出java.io.NotSerializableException
。这是为何呢?Serializable只是一个空接口,如何实现的呢?
其实这个问题也很好回答,咱们再回到刚刚ObjectOutputStream的writeObject的调用栈:
writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject
writeObject0方法中有这么一段代码:
if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } }
在进行序列化操做时,会判断要被序列化的类是不是Enum、Array和Serializable类型,若是不是则直接抛出NotSerializableException
。
问:Serializable明明就是一个空的接口,它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢?
答:在类的序列化过程当中,会使用instanceof关键字判断一个类是否继承了Serializable类,若是没有,则直接抛出NotSerializableException异常。
一、若是一个类想被序列化,须要实现Serializable接口。不然将抛出NotSerializableException
异常,这是由于,在序列化操做过程当中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。
二、在变量声明前加上该关键字,能够阻止该变量被序列化到文件中。
三、在类中增长writeObject 和 readObject 方法能够实现自定义序列化策略。