您的位置:首页 > 编程语言 > Java开发

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

2014-01-13 15:32 736 查看
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?我没有想明白,但是在stackoverflow上问了这个问题之后,得到了大神的帮助。由于有可能键值对存在类似于<"key1",this>这样的方式,如果不加控制的话,会造成死循环的调用hashCode,因此,需要使用loadFactor作为标志,如果调用时小于0,说明进入了死循环,直接就返回0。神作啊!详情移步http://stackoverflow.com/questions/21085135/how-to-understand-the-hashcode-funtion-of-java-util-hashtable-in-the-source-code

另,HashMap中并没有看到对于hashCode的实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: