首页 > IT职业 > IT生活 > HashTable源码分析
前面几篇介绍了List相关的几个类。本篇开始分析Map相关的集合常用类的源码,OK,从HashTable开始分析。我们知道HashTable是线程安全的,但是其实现实中我们使用它的概率却比线程不安全的HashMap要低,为什么呢?
内部数据结构
// 哈希表,内部使用单链表解决冲突,容量不足也会自动增长,transient保证不会被序列化private transient Entry,?>[] table;// 当前容量大小private transient int count;// HashTable扩容的阈值,它为int (table.length * loadFactor)private int threshold;// 负载因子private float loadFactor;// 注意,以上几个参数在HashMap中也是类似的,务必需要理解// HashTable的数据结果,在HashMap中已经是Node了private static class Entryimplements Map.Entry { final int hash; final K key; V value; Entry next; protected Entry(int hash, K key, V value, Entry next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } // ... public V setValue(V value) { if (value == null) throw new NullPointerException(); V oldValue = this.value; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry,?> e = (Map.Entry,?>)o; return (key==null ? e.getKey()==null : key.equals(e.getKey())) && (value==null ? e.getValue()==null : value.equals(e.getValue())); } public int hashCode() { return hash ^ Objects.hashCode(value); } public String toString() { return key.toString()+"="+value.toString(); } }
其实从这里就可以看出HashTable的整个数据结构了:数组 + 链表。下面就通过实践来验证下。
还是通过一个简单的小Demo,通过debug来分析源码。
public class Demo05 {
public static void main(String[] args) {
Map table = new Hashtable() ;
table.put(1, "java");
}
}// 默认构造函数public Hashtable() { // 初始化容量大小为11,初始化负载因子0.75
this(11, 0.75f);
}public Hashtable(int initialCapacity, float loadFactor) { if (initialCapacity [initialCapacity]; // 默认为 11 * 0.75 = 8
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
从上面可以看出,HashTable在构造函数主要完成了两件事
初始化table数组
计算threshold阈值
ok,我们继续查看插入数据方法
// 线程安全但同时效率不高public synchronized V put(K key, V value) { // Make sure the value is not null
// 这里可以看出HashTable的value不可以为null
if (value == null) { throw new NullPointerException();
} // Makes sure the key is not already in the hashtable.
Entry,?> tab[] = table; // 调用key的hashCode()说明HastTable的key不可以为null
int hash = key.hashCode(); // 高位取余
int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") // 从哈希表中获取对应index位置的entry
Entry entry = (Entry)tab[index]; // 从该entry中遍历其链表,如果有hash相同或者key相等的则覆盖返回旧值
for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value; return old;
}
}
addEntry(hash, key, value, index); return null;
}
从这个方法我们可以看出,它使用了synchronized锁,如果看其它的方法,其实HashTable的每个方法都条件了synchronized,它保证了线程安全。但是我们知道这样实现肯定浪费性能,尤其是在并发情况下
这个方法主要完成了如下几件事情
判断value是否为null,即HashTable不允许value为null
计算key的hashcode,也可以看出不允许key为null,否则也会报空指针异常
通过hashcode计算应该插入的角标值index
遍历当前table数组,查看是否有hash值相等并且key相等的entry,如果有则覆盖,如果没有调用addEntry()方法
继续跟踪addEntry方法
// 添加一个entryprivate void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry,?> tab[] = table; // 如果当前table的容量超过阈(yu)值
if (count >= threshold) { // Rehash the table if the threshold is exceeded
rehash(); // 重新复制扩容后的table、hash、index
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
} // Creates the new entry.
@SuppressWarnings("unchecked")
Entry e = (Entry) tab[index];
tab[index] = new Entry(hash, key, value, e);
count++;
}
此方法主要完成:
判断当前table容量是否达到阈值,如果达到阈值则进行扩容
如果没有达到阈值,则直接new Entry()并添加到Entry中
因为我们第一次添加,因此肯定不会进入到扩容。但是我们还是看一下扩容的实现吧。
// 扩容protected void rehash() { int oldCapacity = table.length;
Entry,?>[] oldMap = table; // overflow-conscious code
// 当前容量*2+1
int newCapacity = (oldCapacity 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
} // 创建新的哈希表
Entry,?>[] newMap = new Entry,?>[newCapacity];
modCount++; // 计算阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap; // 将old里的entry重新计算hash定位到new
for (int i = oldCapacity ; i-- > 0 ;) { for (Entry old = (Entry)oldMap[i] ; old != null ; ) {
Entry e = old;
old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry)newMap[index];
newMap[index] = e;
}
}
}
再来理下HashTable的扩容步骤:
计算新数组的容量:(old
根据新容量创建新数组大小,重新计算阈值
将old数组里面的元素重新计算新的index并赋值
本篇主要介绍了HashMap整体数据结构,数据扩容机制几点。只是介绍了put()方法,因为其它的像get()、remove()方法比较简单,就没有分析了。可能还有我没有注意的难点,欢迎留言~~~
作者:godfather2
链接:https://www.jianshu.com/p/690e3f27a353
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/3500/viewspace-2809650/ ,如需转载,请注明出处,否则将追究法律责任。