前言
在剖析Set,Map,List等集合类,序列化及反序列化的原理之前,先介绍一下transient关键字的作用,java提供了transient关键字,用于修饰变量,标明该变量不用序列化到字节流中。同时,java也提供了Serializable这样一个标志性接口(不含任何方法,仅仅只是标明该对象可以被序列化)
使用示例:// Serializable 标志性接口
static class User implements Serializable
{
private static final long serialVersionUID = 1L;
private String username = null;
//使用 transient 关键字标明该变量值不会被序列化到字节流中
private transient String password = null;
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
}
特例剖析
了解了transient关键字及Serializable标志性接口的作用后,下面来看几个特殊的例子。Set,Map,List等集合类的序列化实现。
JDK源码如下(HashMap为例):public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
// The default initial capacity - MUST be a power of two.
static final int DEFAULT_INITIAL_CAPACITY = 16;
// The table, resized as necessary. Length MUST Always be a power of two.
transient Entry[] table; //存放数据的table
// The number of key-value mappings contained in this map.
transient int size;//数据的个数
}
通过JDK源码发现,HashMap对于存放数据的变量table和大小size,都使用了transient关键字修饰(即不序列化该变量)。那么HashMap又是如何完成对数据序列化的呢?继续阅读JDK源码,发现HashMap自己实现了一套writeObject,和readObject方法。/**
* Save the state of the <tt>HashMap</tt> instance to a stream (i.e.,
* serialize it).
* @serialData The <i>capacity</i> of the HashMap (the length of the
* bucket array) is emitted (int), followed by the
* <i>size</i> (an int, the number of key-value
* mappings), followed by the key (Object) and value (Object)
* for each key-value mapping. The key-value mappings are
* emitted in no particular order.
*/
private void writeObject(java.io.ObjectOutputStream s) throws IOException
{
Iterator<Map.Entry<K,V>> i = (size > 0) ? entrySet0().iterator() : null;
// Write out the threshold, loadfactor, and any hidden stuff
s.defaultWriteObject();
// Write out number of buckets
s.writeInt(table.length);
// Write out size (number of Mappings)
s.writeInt(size);
// Write out keys and values (alternating)
if (i != null)
{
while (i.hasNext()) {
Map.Entry<K,V> e = i.next();
s.writeObject(e.getKey());
s.writeObject(e.getValue());
}
}
}
/**
* Reconstitute the <tt>HashMap</tt> instance from a stream (i.e.,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
{
// Read in the threshold, loadfactor, and any hidden stuff
s.defaultReadObject();
// Read in number of buckets and allocate the bucket array;
int numBuckets = s.readInt();
table = new Entry[numBuckets];
// Give subclass a chance to do its thing.
init();
// Read in size (number of Mappings)
int size = s.readInt();
// Read the keys and values, and put the mappings in the HashMap
for (int i=0; i<size; i++) {
K key = (K) s.readObject();
V value = (V) s.readObject();
putForCreate(key, value);
}
}
当使用ObjectOutputStream writeObject序列化对象时,如果该对象有writeObject方法则调用该对象的writeObject方法(通过反射实现),这样达到序列化重写的目的。
JDK源码如下:/**
* Writes instance data for each serializable class of given object, from
* superclass to subclass.
* ObjectOutputStream writeObject会调用writeSerialData完成对实现Serializable标志性接口的序列化
*/
private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
//待序列化对象是否包含writeObject方法
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
if (extendedDebugInfo) {
debugInfoStack.push("custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
SerialCallbackContext oldContext = curContext;
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
//通过反射调用对象自己的writeObject方法
slotDesc.invokeWriteObject(obj, this)
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
//待序列化的对象没有writeObject方法,则使用JDK默认的
defaultWriteFields(obj, slotDesc);
}
}
}
总结
Map,Set,List等都是使用transient关键字屏蔽变量,然后自己实现的序列化操作。