【jdk源码解析三】java.util.Hashtable

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 zeroloadFactor = -loadFactor; // Mark hashCode computation in progressEntry[] 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 completereturn h;}为什么会要用到loadFactor?每次计算为什么要先乘个-1?我没有想明白,但是在stackoverflow上问了这个问题之后,得到了大神的帮助。由于有可能键值对存在类似于<"key1",this>这样的方式,如果不加控制的话,会造成死循环的调用hashCode,因此,需要使用loadFactor作为标志,如果调用时小于0,说明进入了死循环,直接就返回0。神作啊!详情移步

另,HashMap中并没有看到对于hashCode的实现。

任何业绩的质变都来自于量变的积累。

【jdk源码解析三】java.util.Hashtable

相关文章:

你感兴趣的文章:

标签云: