1.HashMap与HashTable的对比
对比点 | HashMap | HashTable |
---|---|---|
线程安全 | 否 | 是 |
效率 | 高 | 低 |
null key, null value的支持 | 是 | 否 |
2.HashMap与HashTable扩容的不同点
- 创建时如果不指定初始容量HashTable的初始容量为11,负载因子为0.75
- 扩容的时机,当entry的数量大于阈值((int)(capacity * loadFactor)),每次扩容,容量变为原来的2n+1
/**
* Constructs a new, empty hashtable with a default initial capacity (11)
* and load factor (0.75).
*/
public Hashtable() {
this(11, 0.75f);
}
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code 扩容为原来的2倍+1
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 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;
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
- HashMap 默认初始值为16,每次扩容后容量变为原来的2倍
- 如果指定了初始容量,Hashtable会直接使用该初始容量,HashMap会将其扩充为2的次幂大小,HashMap的容量总是2的次幂
3.底层数据结构
JDK1.8之后的HashMap在解决hash冲突时有了较大变化,当链表长度大于阈值(默认位8)时将链表转化为红黑树,减少搜素时间。注意:将链表转化为红黑树前会进行判断,如果当前数组长度小于64时,会首先进行数组扩容,而不是转为红黑树。
public V put(K key, V value) {
//取key的hash值
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//进行赋值并判读数组是否为null或者数组长度是否为0,初次构建的HashMap对象第一次put操作时table均为null
if ((tab = table) == null || (n = tab.length) == 0)
//如果数组为null或者长度为0则进行扩容
n = (tab = resize()).length;
//(n-1)& hash 此处计算在数组中的位置(下标),假如n=16
// 0000 0000 0000 1111 & hash
// 结果 前12位均为 0, 后四位参与运算,这时会体现出扰动函数hash() 的作用,
// 如果不进行扰动处理,key 的hashcode 参与计算的只有低16位,高16位进行&操作时全部为0,
//需要进行扰动,使得高16位进来,降低hash冲突。最终计算存储数组位置的下标时,
//实际参与运算又只有扰动后的最后四位。
//
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断是不是树节点,
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//循环链表,并统计链表长度,某个节点的下一个节点为null时,
// 将该节点的next 指向 新节点
// 判断节点数量 是否大于等于 8(转换为红黑树的阈值)
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 转换为红黑树会先判断数组长度是否 小于 64 如果小于则先进行数组扩容
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
//扰动函数 用于获取Key的hash值,hash值右移16位并取异或,使得hash值得高16位和低16位都参与进来。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
* Replaces all linked nodes in bin at index for given hash unless
* table is too small, in which case resizes instead.
*/
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//如果数组的长度小于64 先进行数组扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
//转为树
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
待续。。。。