当我们 new 一个 HashSet 的时候,查看其源码,会发现一件很有意思的事情:HashSet 的底层竟然是 HashMap !
private transient HashMap<E,Object> map;
//HashSet构造器
public HashSet() {
map = new HashMap<>();//new 一个 HashMap对象
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
//三个HashMap构造器
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
HashMap 是由数组和链表以及红黑树构成的。
为了弄清楚其扩容机制,我们查看其 add 方法的源码:
private static final Object PRESENT = new Object();//空对象
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
追根溯源,继续往下探究。(下面代码很长,请耐心观看)
transient Node<K,V>[] table;//Node数组
int threshold;//该数值决定什么时候开始下一次扩容
transient int modCount;
transient int size;
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//计算 key 的 hash 值
static final int hash(Object key) {
int h;
// key 为空则返回 0 ,否则返回 key 的 hashCode 值 与 h >>> 16 二者经过位运算符 ^ 运算得到的值
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
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 就是HashMap 的一个数组,类型是Node[]
//如果当前 tab 为 null 或者大小为 0 ,则将 tab 扩容至 16
if ((tab = table) == null || (n = tab.length) == 0)
//resize()方法请看后面讲解
n = (tab = resize()).length;//n = 16
//计算该 key 应该存放到 tab 表的哪个索引位置,并把该位置的对象赋值给 p
//元素的位置要么是在原位置,要么是在原位置加原数组长度的位置。
//如果 p 为 null, 表示还没有存放元素,就创建一个 Node 并放在该位置
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果 p 所在位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样
//并且满足两个条件之一:
//(1)准备加入的key 和 p 指向的 Node 结点的 key 是同一个对象
//(2)p 指向的 Node 结点的 key 和 准备加入的 key 比较后相同 (equals方法可能重写)
// 就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断 p 是不是一颗红黑树
//如果是一颗红黑树,就调用 putTreeVal ,来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果 tab 对应索引位置,已经是一个链表,就使用for循环比较
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
//比较结束都不相同,则加入到链表末端
p.next = newNode(hash, key, value, null);
//如果此时链表长度已达8,则对当前链表进行树化(转成红黑树)
//(-1是第一个结点在table表中,这里没有计算)
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//比较
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) {
V oldValue = e.value;
//onlyIfAbsent 为 false,取反为 true
if (!onlyIfAbsent || oldValue == null)
//更改 value
e.value = value;
//空方法,可改写
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
//此处为空方法,可改写用于检验数据
afterNodeInsertion(evict);
return null;
}
resize方法是HashMap扩容的重要方法。resize方法源码如下:
int threshold;//临界值,该数值决定什么时候开始下一次扩容
static final int MAXIMUM_CAPACITY = 1 << 30;//很大的数,基本不会碰上
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //16
static final float DEFAULT_LOAD_FACTOR = 0.75f;//负载因子,决定临界值大小
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;//获得table大小
int oldThr = threshold;//获得临界值,新数组则为0
int newCap, newThr = 0;//新数组容量和临界值
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//原数组大小大于16时,临界值设置为原来的两倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0)//意味着老数组没有元素
//初始容量设置为临界值
newCap = oldThr;
else {
//说明是调用无参构造器创建的旧数组,并且第一次添加元素
newCap = DEFAULT_INITIAL_CAPACITY;//16
//新临界值 = 负载因子 * 默认容量
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//0.75 * 16
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//将新临界值赋值给 threshold
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//new 一个新的 Node 数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//原数组不为空,说明是扩容操作,则涉及到元素转移操作
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//如果当前位置元素不为空,那么需要转移该元素到新数组
if ((e = oldTab[j]) != null) {
//置空 oldTab[j],便于虚拟机回收
oldTab[j] = null;
//如果 e 的后结点为空,则计算 e 在 newTab 中的位置并置入
if (e.next == null)
//如果这个oldTab[j]就一个元素,那么就直接放到newTab里面
// 把元素存储到新的数组中,存储到数组的哪个位置需要根据hash值和数组长度来进行取模
// 【hash值 % 数组长度】 = 【 hash值 & (数组长度-1)】
// 数组扩容后,所有元素都需要重新计算在新数组中的位置。
newTab[e.hash & (newCap - 1)] = e;
//如果此时 e 已经转为红黑树结点
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // e 有后结点
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
//重点难点!!!
//与运算 & 是 两个位都为1时,结果才为1
//(e.hash & oldCap) 得到的是 元素在数组中的位置是否需要移动
// 示例1:
// e.hash=10 0000 1010
// oldCap=16 0001 0000
// & =0 0000 0000 比较高位的第一位 0
//结论:元素位置在扩容后数组中的位置没有发生改变
// 示例2:
// e.hash=17 0001 0001
// oldCap=16 0001 0000
// & =1 0001 0000
//结论:元素位置在扩容后数组中的位置发生了改变,新的下标位置是原下标位置+原数组长度
if ((e.hash & oldCap) == 0) {//扩容后不需要移动的链表
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {//扩容后需要移动的链表
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}