超类分析
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap实现了Map、Cloneable、Serializable。
【接口层】java.util.Map<K,V>
【抽象层】java.util.AbstractMap<K,V>
AbstractMap作为Map的骨架实现,基本实现了除put、entrySet之外的方法,主要减少实现Map的工作量。
而put、entrySet实现取决于Map的数据存储结构,AbstractMap将其交由子类实现。
[size, isEmpty, containsKey, containsValue, get, remove, clear]
上述方法基于entrySet视图实现,部分代码如下:
public int size() {
return entrySet().size();
}
...
// 基于entrySet的迭代器遍历查找V
public V get(Object key) {
Iterator<Entry<K,V>> i = entrySet().iterator();
略... 考虑key为null的情况
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
return null;
}
...
public void clear() {
entrySet().clear();
}
[putAll]
基于put方法实现
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
[keySet, values]
基于entrySet的迭代器创建keySet、values视图(适配器模式?)
// keySet视图
transient Set<K> keySet;
// values视图
transient Collection<V> values;
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
// 基于entrySet迭代器构建AbstractSet匿名内部类作为keySet视图
ks = new AbstractSet<K>() {
public Iterator<K> iterator() {
return new Iterator<K>() {
private Iterator<Entry<K,V>> i = entrySet().iterator();
public boolean hasNext() { return i.hasNext(); }
public K next() { return i.next().getKey(); }
public void remove() { i.remove(); }
};
}
public int size() { return AbstractMap.this.size(); }
public boolean isEmpty() { return AbstractMap.this.isEmpty(); }
public void clear() { AbstractMap.this.clear(); }
public boolean contains(Object k) { return AbstractMap.this.containsKey(k); }
};
keySet = ks;
}
return ks;
}
public Collection<V> values() {
Collection<V> vals = values;
if (vals == null) {
// 同keySet, 考虑value的不唯一性,构建AbstractCollection匿名内部类
vals = new AbstractCollection<V>() {
...
};
values = vals;
}
return vals;
}
上述代码中keySet 和 values视图作为成员变量,存在线程安全问题。
本段介绍并分析了HashMap的超类,可能会有部分同学认为HashMap继承了AbstractMap,只需实现put、entrySet方法即可。
事实上HashMap重写了Map接口的所有方法,为什么HashMap需要重写AbstractMap已经实现的方法呢?
这个问题待各位同学看完全文之后再做思考。
HashMap源码分析
上图仅列出了HashMap的成员变量及重要的实现方法(视图另外说明)。
本文将从以下几个方面对HashMap源码进行分析
- 散列算法
- 数据结构
- 初始化及扩容机制
- 视图
散列算法
HashMap的散列算法 (len - 1) & hash
// putVal中通过散列算法计算节点下标
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
// tab=节点数组; p = 旧节点 不为空 => hash冲突
// n=数组长度; i=数组下标(散列算法的结果值)
Node<K,V>[] tab; Node<K,V> p; int n, i;
// n = tab.length 赋值n为节点数组的长度
if ((tab = table) == null || (n = tab.length) == 0)
...
// i = (n - 1) & hash 通过散列算法计算下标i, 并取该下标节点赋值给p
if ((p = tab[i = (n - 1) & hash]) == null)
...;
else {
... 解决hash冲突
}
... 新增/覆盖操作之后 扩容判断 + 新增回调 + 返回旧节点
}
Ps: hash冲突 => hash相同;
HashMap不允许重复的key, 为什么hash会相同? => euqals相同,hash一定相同; hash相同, equals不一定相同;
如何减少hash冲突?
HashMap的散列算法表达式 (len - 1) & hash 中只存在两个变量。
len优化
如何优化len呢?
基于散列算法采用&位运算符, 当len为2的幂次方时hash冲突概率最低,分析如下:
设:hash1 = 11(1011); hash2 = 9(1001)
len为32时 len - 1 = 31(0001 1111)
hash1 & 31 = 11(1011); hash2 & 31 = 9(1001)
此时无hash冲突。
len为30时 len - 1 = 29(0001 1101)
hash1 & 29 = 9(1001); hash2 & 29 = 9(1001)
因为29的低2位为0,导致 hash1和hash2的低2位的数据丢失,散列值相同造成hash冲突。
// 在指定初始容量(initialCapacity)的构建函数中对len进行了优化
public HashMap(int initialCapacity, float loadFactor) {
... 前置校验
this.loadFactor = loadFactor; // 加载因子
// threshold => 容量扩容的临界值, 也可记录初始容量。
// tableSizeFor(initialCapacity) 对传入的容初始量值做处理
this.threshold = tableSizeFor(initialCapacity);
}
/** 将长度扩为2的幂次方
* 思路:
* 1 循环
* int newCap = 1;
* while((newCap = newCap << 1) < cap);
* return newCap;
* 2 二进制
* 如 17的二进制为 (0001 0001)
* 最高有效位为第五位 (8765 4321)下标
* 将最高有效位(5)后 (0001 1111) => 31 + 1
* 都设为1即可,并+1即可
* 实现思路如下
*/
static final int tableSizeFor(int cap) {
int n = cap - 1; // 假设 cap=17 n = 16(0001 0000)
n |= n >>> 1; // n=>24(0001 1000) = 16(0001 0000) |= 8(1000)
n |= n >>> 2; // n=>30(0001 1110) = 24(0001 1000) | = 6(0110)
n |= n >>> 4; // n=>31(0001 1111) = 30(0001 1110) | = 1(0001)
n |= n >>> 8; // n=>31(0001 1111) = 31(0001 1111) | = 0(0000)
n |= n >>> 16; // n=>31(0001 1111) = 31(0001 1111) | = 0(0000)
// return 32(31 + 1)
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
hash优化
HashMap的默认容量为 1<<4(16),最大容量为 1<<30(1,073,741,824)
// 默认初始容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 容量最大值
static final int MAXIMUM_CAPACITY = 1 << 30;
HashMap的容量值通常不会太大,导致hash的高位不会参与散列算法。
所以HashMap通过将hash ^ hash的高16位,实现高16位参与散列算法,降低hash冲突的概率。
static final int hash(Object key) {
int h;
// 当key非空时,取key的hash ^ key的hash >>> 16(即hash的高16位)
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
数据结构
有空再写
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。