前两节已经讲得很清楚,HashMap的核心是一个Node数组,源码定义如下:
transient Node<K,V>[] table;
我们发现,数组被transient关键字修饰,而transient与序列化有关。所谓序列化,是将对象的状态信息转换为可以存储或传输的形式的过程,是一种对象持久化手段。
JVM运行时,创建的对象都是存储在内存的堆中,如果JVM被shutdown或者不存在引用指向该对象,对象就会被消除。但在有些情况下我们希望能将对象保存下来,等到JVM重启后能够再次被读到内存,或者我们希望将对象通过网络传输给另一台JVM(例如RPC调用),这就需要将对象持久化。
无论是将对象保存到本地硬盘,还是通过网络传输,都必须将对象转化成二进制,才能进行IO传输。Java的对象序列化机制能够将对象状态(对象序列化只会序列化对象的成员变量,不会关注类中的静态变量)转化成字节数组,并且在未来将字节数组反序列化成对象。
Transient关键字的作用是控制变量的序列化。在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值,如int型的是0,对象型的是null。
那问题来了,Node数组table存储了HashMap的整个数据结构,可HashMap却决定将如此核心的数据免于被序列化,这是为啥?

我们知道,HashMap是根据key的hashCode来确定这组键值对应该放在数组哪个位置的。Object.hashcode()是native方法,不同的JVM、不同的操作系统得到的hashCode是不一样的。因此,同样的键值对数据存入不同的JVM机器上的HashMap,生成数组的数据结构是不一样的。所以如果我们只是单纯地将数组序列化,然后传输给另一台JVM直接使用,肯定会出问题。所以,table里虽然存储了核心数据,但其数据结构不是在任何JVM平台都是通用的,所以没必要将其序列化。

那HashMap是如何解决序列化的问题的呢?其实HashMap内定义了两个方法writeObject和readObject。
在序列化过程中,如果被序列化的类中定义了writeObject和readObject方法,虚拟机会试图调用对象类里的writeObject和readObject方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,则默认调用是ObjectOutputStream的 defaultWriteObject方法以及ObjectInputStream的defaultReadObject方法。咱举个例子:
public class Student extends People implements Serializable{private static final long serialVersionUID = 1L;transient public String name;Student (String name) {this.name = name;}private void writeObject(ObjectOutputStream s) throws IOException {System.out.println("调用了writeObject方法");s.writeObject(name);}private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {System.out.println("调用了readObject方法");this.name = (String) s.readObject();}public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {ObjectOutputStream oop = new ObjectOutputStream(new FileOutputStream("D:/a.txt"));Student s = new Student("MR.盛");oop.writeObject(s);ObjectInputStream oip = new ObjectInputStream(new FileInputStream("D:/a.txt"));Student s1 = (Student)oip.readObject();System.out.println(s1.name);}}//输出://调用了writeObject方法//调用了readObject方法//MR.盛
HashMap的writeObject源码如下:
private void writeObject(java.io.ObjectOutputStream s) throws IOExceptionint buckets = capacity();s.defaultWriteObject();s.writeInt(buckets);s.writeInt(size);internalWriteEntries(s);}void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {Node<K,V>[] tab;if (size > 0 && (tab = table) != null) {for (int i = 0; i < tab.length; ++i) {//一个一个将键值对写到输出流中for (Node<K,V> e = tab[i]; e != null; e = e.next) {s.writeObject(e.key);s.writeObject(e.value);}}}}
HashMap的readObject源码如下:
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException// Read in the threshold (ignored), loadfactor, and any hidden stuffs.defaultReadObject();reinitialize();if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new InvalidObjectException("Illegal load factor: " +loadFactor);s.readInt(); // Read and ignore number of bucketsint mappings = s.readInt(); // Read number of mappings (size)if (mappings < 0)throw new InvalidObjectException("Illegal mappings count: " +mappings);else if (mappings > 0) { // (if zero, use defaults)// Size the table using given load factor only if within// range of 0.25...4.0float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);float fc = (float)mappings / lf + 1.0f;int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?DEFAULT_INITIAL_CAPACITY :(fc >= MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY :tableSizeFor((int)fc));float ft = (float)cap * lf;threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?(int)ft : Integer.MAX_VALUE);// Check Map.Entry[].class since it's the nearest public type to// what we're actually creating.SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] tab = (Node<K,V>[])new Node[cap];table = tab;// Read the keys and values, and put the mappings in the HashMapfor (int i = 0; i < mappings; i++) {@SuppressWarnings("unchecked")K key = (K) s.readObject();@SuppressWarnings("unchecked")V value = (V) s.readObject();putVal(hash(key), key, value, false, false);}}}
可见,readObject做的事情就是将键值对一个个读取出来,然后按照put方法的逻辑生成当前机器可以使用的数据结构。

HashMap就讲这么多,完结撒花。。。。。
下期,我将开始全面解析JDK1.7 和 JDK1.8 的ConcurrentHashMap 源码,敬请期待~~~




