今天就来好好剖析一下HashMap的源码吧~
不说废话,直接上源码
- 先说一下参数
Node<K,V>[] table:真正储存键值对
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//初始化容量为16(桶的数量)
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;//负载因子:0.75
static final int TREEIFY_THRESHOLD = 8;//树化门限值:8
static final int UNTREEIFY_THRESHOLD = 6;//解树化,返回链表的阈值:6
static final int MIN_TREEIFY_CAPACITY = 64; //树化的最少元素个数:64
transient Node<K,V>[] table;
final float loadFactor;//负载因子
- HashMap的无参构造
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; //初始化加载容量
}
由此可以看出HashMap也是懒加载策略
- HashMap的put方法
public V put(K key,V value) {
return putVal(hash(key), key, value, false, true);
}
调用put()实际上调用的是putVal()
,那接下来就看一下putVal()
,后面的hash()方法是用来求哈希值的,我们稍后再讲
- putVal()
//hash: hash for key(求key的hash值)
//key: the key
//value: the value to put
//onlyIfAbsent: if true, don't change existing value()(如果参数为true,不改变value值)
//evict: if false, the table is in creation mode.(如果参数为false,表为初始化模式)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//当table未初始化即哈希桶的未进行初始化时
if ((tab = table) == null || (n = tab.length) == 0)
//调用resize方法初始化table,n记录初始化后桶的个数
n = (tab = resize()).length;
//(n - 1) & hash用来求hash%(n-1),该语句用来判断hash桶中是不是装有结点
if ((p = tab[i = (n - 1) & hash]) == null)
//hash桶中未装有结点,将key和value值封装成一个node结点放入对应的桶中
tab[i] = newNode(hash, key, value, null);
//若hash桶中装有结点
else {
Node<K,V> e; K k;
//当put的key值是已有值时
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 {
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
treeifyBin(tab, hash);
break;
}
//若该key值已经存在,跳出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//若key值重复,更新value值
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;
}
- hash()
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
hash()实际上是对hashCode()进行一层包装
return语句:若key为null则返回hash值0,否则需调用hashCode()求hash值。
求出key的hashCode值,将其与h右移16位进行异或。
注意:>>>是无符号的右移,>>是有符号的右移
问题1:
那么我们不得不思考一个问题,求hash值直接调用hashCode()不就可以了吗,为什么还要进行一层包装呢?
先来看一下hashCode()方式是如何定义的
public native int hashCode();
我们可以看到hashCode()的返回值是int类型,int大概是2^31左右,如果hash桶的数量这么大,基本上不会出现哈希碰撞,那么也就不会对哈希桶进行扩容,那么HashMap使用的哈希表和数组也就没什么区别了。
问题2:
为何取出key值得高16位右移参与hash运算?
因为hash基本上是在高16位进行hash运算
问题3:
为何hashMap的容量为2^n
(n -1)&hash :当n为2^n,此时的位运算就相当于hash%(n-1)
内容并不是特别详细,欢迎交流~