若是你有仔细阅读过HashMap的源码,那么你必定注意过一个问题:HashMap中有两个私有方法。
private void writeObject(java.io.ObjectOutputStream s) throws IOException
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
这两个方法有两个共同点:java
答:readObject和writeObject方法都是为了HashMap的序列化而建立的。 首先,HashMap实现了Serializable接口,这意味着该类能够被序列化,而JDK提供的对于Java对象序列化操做的类是ObjectOutputStream,反序列化的类是ObjectInputStream。咱们来看下序列化使用的ObjectOutputStream
,它提供了不一样的方法用来序列化不一样类型的对象,好比writeBoolean,wrietInt,writeLong等,对于自定义类型,提供了writeObject方法。 ObjectOutputStream的writeObject方法会调用下面的方法:segmentfault
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; if (slotDesc.hasWriteObjectMethod()) {//若是重写了writeObject方法 PutFieldImpl oldPut = curPut; curPut = null; SerialCallbackContext oldContext = curContext; try { curContext = new SerialCallbackContext(obj, slotDesc); bout.setBlockDataMode(true); slotDesc.invokeWriteObject(obj, this); //调用实现类本身的writeobject方法 bout.setBlockDataMode(false); bout.writeByte(TC_ENDBLOCKDATA); } finally { //省略 } curPut = oldPut; } else { defaultWriteFields(obj, slotDesc); } } }
或者下面这张图也能够: 能够看到,实际上在ObjectOutputStream中进行序列化操做的时候,会判断被序列化的对象是否本身重写了writeObject方法,若是重写了,就会调用被序列化对象本身的writeObject方法,若是没有重写,才会调用默认的序列化方法。 调用关系以下图:
数组
JDK文档中并无明确说明设置为私有的缘由。方法是私有的,那么该方法没法被子类override,这样作有什么好处呢? 若是我实现了一个继承HashMap的类,我也想有本身的序列化和反序列化方法,那我也能够实现私有的readObject和writeObject方法,而不用关心HashMap本身的那一部分。 下面的部分来自StackOverFlow:ide
We don't want these methods to be overridden by subclasses. Instead, each class can have its own writeObject method, and the serialization engine will call all of them one after the other. This is only possible with private methods (these are not overridden). (The same is valid for readObject.)this
首先要明确序列化的目的,将java对象序列化,必定是为了在某个时刻可以将该对象反序列化,并且通常来说序列化和反序列化所在的机器是不一样的,由于序列化最经常使用的场景就是跨机器的调用,而序列化和反序列化的一个最基本的要求就是,反序列化以后的对象与序列化以前的对象是一致的。 HashMap中,因为Entry的存放位置是根据Key的Hash值来计算,而后存放到数组中的,对于同一个Key,在不一样的JVM实现中计算得出的Hash值多是不一样的。 Hash值不一样致使的结果就是:有可能一个HashMap对象的反序列化结果与序列化以前的结果不一致。即有可能序列化以前,Key='AAA'的元素放在数组的第0个位置,而反序列化值后,根据Key获取元素的时候,可能须要从数组为2的位置来获取,而此时获取到的数据与序列化以前确定是不一样的。 在《Effective Java》中,Joshua大神对此有所解释:code
For example, consider the case of a hash table. The physical representation is a sequence of hash buckets containing key-value entries. The bucket that an entry resides in is a function of the hash code of its key, which is not, in general, guaranteed to be the same from JVM implementation to JVM implementation. In fact, it isn't even guaranteed to be the same from run to run. Therefore, accepting the default serialized form for a hash table would constitute a serious bug. Serializing and deserializing the hash table could yield an object whose invariants were seriously corrupt.orm
因此为了不这个问题,HashMap采用了下面的方式来解决:对象
transient
关键字修饰,从而避免JDK中默认序列化方法对该对象的序列化操做。不序列化的包括:Entry[] table
,size
,modCount
。想一想看,是否是可以解决序列化和反序列化不一致的状况呢? 因为不序列化存放元素的Entry数组,而是反序列化的时候从新生成,这样就避免了反序列化以后根据Key获取到的元素与序列化以前获取到的元素不一样。blog