HashMap是Hashtable的非线程安全版。非常明显由源码可以看出,Hashtable与HashMap并不出于同一个人之手,代码风格有很大差别。
首先,Hashtable继承自Dictionary接口而不是Map接口,为什么呢?Dictionary接口其实与Map接口差不多,但是已经被废弃,被Map接口所取代。与HashMap相同,Hashtable也实现了java.lang.Cloneable接口与java.io.Serializable接口。
数据结构上,Hashtable与HashMap的结构是几乎一样的实现。
那么Hashtable在源码大体结构与HashMap相似的情况下,有哪些区别呢?首先,Hashtable是线程安全的,其大多数方法是synchronized()的,当然构造方法除外(构造器不能用abstract,final,static,synchronized及native来修饰只能是public ,protected,private)。
其次,Hashtable的初始默认桶容量为11,默认装载因子为0.75,而HashMap则是16和0.75。
然后,Hashtable的索引计算方法与HashMap完全不同,实际上,HashMap会使用一个非常好用的哈希函数来对键的hashCode做哈希,然后将其映射到Entry数组的对应位置,而Hashtable则将键值的hashCode直接与0x7fffffff直接相与,然后模数组的长度,这里就可以看出,由于Hashtable是早期版本,因此,并没有想出像HashMap那样,使用桶容量必须是2的幂次以及相应的求数组索引的方法的一系列优化措施,因此,这里的模运算不能不说是一个非常耗时的操作,相比HashMap的实现,就没有那么精妙。
public synchronized boolean containsKey(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return true; } } return false;
然后是关于扩容。Hashtable采用的也是倍增的方式来实现的扩容,由于其没有桶容量必须是2的幂次的要求,因此,其扩容时实际上是采用2倍加1的方式来扩容。
protected void rehash() { int oldCapacity = table.length; Entry[] oldMap = table; int newCapacity = oldCapacity * 2 + 1; Entry[] newMap = new Entry[newCapacity]; modCount++; threshold = (int)(newCapacity * loadFactor); table = newMap; for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = newMap[index]; newMap[index] = e; } } }关于Hashtable,还有一点,Hashtable实现了map自己的hashCode。这个函数应该是神来之笔,因为其实现是很巧妙的。
public synchronized int hashCode() { /* * This code detects the recursion caused by computing the hash code * of a self-referential hash table and prevents the stack overflow * that would otherwise result. This allows certain 1.1-era * applets with self-referential hash tables to work. This code * abuses the loadFactor field to do double-duty as a hashCode * in progress flag, so as not to worsen the space performance. * A negative load factor indicates that hash code computation is * in progress. */ int h = 0; if (count == 0 || loadFactor < 0) return h; // Returns zero loadFactor = -loadFactor; // Mark hashCode computation in progress Entry[] tab = table; for (int i = 0; i < tab.length; i++) for (Entry e = tab[i]; e != null; e = e.next) h += e.key.hashCode() ^ e.value.hashCode(); loadFactor = -loadFactor; // Mark hashCode computation complete return h; }
为什么会要用到loadFactor?每次计算为什么要先乘个-1?我没有想明白,但是在*上问了这个问题之后,得到了大神的帮助。由于有可能键值对存在类似于<"key1",this>这样的方式,如果不加控制的话,会造成死循环的调用hashCode,因此,需要使用loadFactor作为标志,如果调用时小于0,说明进入了死循环,直接就返回0。神作啊!详情移步http://*.com/questions/21085135/how-to-understand-the-hashcode-funtion-of-java-util-hashtable-in-the-source-code
另,HashMap中并没有看到对于hashCode的实现。