目录
一、类属性
/**
* 默认的初始容量是16,必须是2的倍数
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 最大容量
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认负载因子
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 桶内节点数大于8个时,存储结构由单向链表转换成红黑树
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 扩容时当桶内节点数小于6个时红黑树转换成单向链表
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
*桶存储结构由列表转换成树时数组的最低容量,低于该容量时通过扩容数组解决部分桶节点过多问题
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/* ---------------- Fields -------------- */
/**
*存储Node的数组,总是2的整数次幂
*/
transient java.util.HashMap.Node<K,V>[] table;
/**
*保存元素的Set集合
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* 包含的键值对个数
*/
transient int size;
/**
*记录修改次数,实现在遍历map时如果修改map会快速失败的功能
*/
transient int modCount;
/**
* 进行扩容的临界值,根据初始容量计算出来的实际最大容量再乘以负载因子
*/
int threshold;
/**
* 负载因子
*/
final float loadFactor;
二、静态方法
/**
* 该方法用于计算key的hash值,然后用该hash值对HashMap的容量取模,算出key存储的位置
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
为什么要做上述的位运算了?首先实际生产中HashMap的容量基本不会超过2^16,即65536个,这种大Map通常都放到缓存组件中了,所以用key的hash值的低16位是最关键的。其次,hashCode()方法返回的是一个int类型,最长32位,右移16位且高位补0(h>>>16)后与原值做异或(两者相同为0,不同为1),相当于高16位和低16位都参与了运算,与原来的只有低16位参与运算相比可以高效的避免hash值不同最后取模结果一样的情形,使key的分散更均匀,提高查找效率。参考如下例子:
keyA hashCode 0000 0000 0000 1100 1010 1111 0000 1000
keyB hashCode 0000 0000 0000 0011 1010 1111 0000 1000
HashMap容量n 0000 0000 0000 0000 0010 0000 0000 0000
n-1 0000 0000 0000 0000 0001 1111 1111 1111
只有低16位参与计算时,hashCode对容量取模的结果都是:
0000 0000 0000 0000 0000 1111 0000 1000
当高16位参与运算时:
keyA h>>>16 0000 0000 0000 0000 0000 0000 0000 1100
^运算 0000 0000 0000 1100 1010 1111 0000 1100
取模 0000 0000 0000 0000 0000 1111 0000 1100
keyB h>>>16 0000 0000 0000 0000 0000 0000 0000 0011
^运算 0000 0000 0000 0011 1010 1111 0000 1011
取模 0000 0000 0000 0000 0000 1111 0000 1011
最终取模的结果不一样
/**
* 该方法用于计算HashMap的实际容量,cap为用户输入的初始容量,实际容量必须是最近的大于或者等于初始容量的2的整数次幂,比如10,则返回16
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
//n最大可能为2^31-1,所以先判断是否大于2^30,如果不是才能加1,否则会超过int类型的最大值
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
参考如下计算过程:
初始值
00000000 11000000 00000000 00010011
n-1
00000000 11000000 00000000 00010010
>>>1
00000000 01100000 00000000 00001001
|=
00000000 11100000 00000000 00011011
>>>2
00000000 00111000 00000000 00000110
|=
00000000 11111000 00000000 00011111
>>>4
00000000 00001111 10000000 00000001
|=
00000000 11111111 10000000 00011111
>>>8
00000000 00000000 11111111 10000000
|=
00000000 11111111 11111111 10011111
>>>16
00000000 00000000 00000000 11111111
|=
00000000 11111111 11111111 11111111
n+1
00000001 00000000 00000000 00000000
从上述计算过程可以发现,其实初始值的低位是没有用到的,关键是初始值的最高位的1,每次右移再做或运算相当于不断的在最高一位1的后面不断填充1,第一次右移1位,填充1个1,第二次右移2位,填充2个1,第三次右移4位,填充4个1,第四次右移8位,填充8个1,最后一次右移16位,填充16个1,如果最高位的1的后面都是1,则后面的位运算无意义。最后通过加1,算出结果。为什么不会最多会右移16位了?因为int是32位,右移16位最多填充31个1,当最高位的1是第一位时,也能让后面都填充为1。
/**
* 如果x实现了Comparable接口则返回x的Class,否则返回null
*/
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) {
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
if ((c = x.getClass()) == String.class) // bypass checks
return c;
//获取该类实现的接口
if ((ts = c.getGenericInterfaces()) != null) {
for (int i = 0; i < ts.length; ++i) {
//如果该类实现了Comparable接口,并且接口的参数化类型就是该类本身
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
/**
*k和x进行比较,要求k实现Comparable接口,x的Class类型就是kc
*/
@SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareTo(x));
}
ParameterizedType接口参考:https://www.cnblogs.com/linghu-java/