add方法,向HashSet集合中添加元素。
我们先看一段代码。
package add;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
HashSet
显而易见,我们创建了泛型为Object类型的list集合,并添加了字符串类型的元素“Tom”。
我们对add方法按ctrl键,查看add方法的底层源代码,结果如下——
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
我们可以看出,e就是我们传入的name,而E指代e 的数据类型,类似于方法传参时的(int a)。
我们传入e,但是还调用了map接口的put方法——
put(K key, V value) 将键(key)/值(value)映射存放到Map集合中
我们知道,在HashMap中key即键是不能重复的,否则覆盖已有的key对相应的值 。
所以HashSet添加的元素也不允许重复。显然key就是我们输入的值,value是add方法之中默认的PRESENT,因为是大写字母,显然是个常量。
继续分析,add方法返回的是一个boolean类型的值,map调用了put方法,其中e是key,而value被设置成了常量。
这行代码的意思是,如果put方法的返回值为null,那么就返回true。
我们继续点开HashMap中的put方法,得到代码——
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
返回的是一个putVal方法,并且初始设定了hash(key), key, value, false, true五个值,其中hash()方法是对key求哈希码。
我们点开putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node 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)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;
}
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;
}
这是本博客最重要的方法,我们先看第一行
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
底层代码用final修饰,表示不可修改,定义了五个传入的参数,分别是int类型的hash,键key,值value,boolean类型的两个参数onlyIfAbsent和evict
Node[] tab; Node p; int n, i;
定义节点Node,其中显然tab类似于数组,而p类似于数组里面的元素。
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
把table赋值给tab,在我们的代码里面并没有出现table,其实点进去就知道,table是HashMap类里面的全局变量,且没有被赋值!
因此,table的初始值为null!也就是说(tab = table) == null成立!因为是或运算,有任一成立即可,继续往下。
我们发现出现了resize方法,顾名思义似乎用来重置长度的,我们点进去——
final Node[] resize() {
Node[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node[] newTab = (Node[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode)e).split(this, newTab, j, oldCap);
else { // preserve order
Node loHead = null, loTail = null;
Node hiHead = null, hiTail = null;
Node next;
do {
next = e.next;
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;
}
这么长的代码不用担心,我们只需要抓住关键几行即可。
首先,定义了oldTab并将table赋值给它
Node[] oldTab = table;
然后在代码的中间出现了这么一行
table = newTab;
很显然,table又被赋值了newTab 的属性。
最后返回的是newTab
return newTab;
那么我们可以确定,newTab等于table,他们的地址指向一致。
现在回归put方法代码
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
newTab又被赋值给了tab,显然tab此时依然等于table。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
hash就是我们在putVal方法最开始输入的hash,他是通过如下方法得到的
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
key即是我们输入add方法中的值
如果key的值为null,返回零,否则返回key 的哈希码(这里规定了哈希码大于等于16)。
也就是说,每输入一个key值,我们给他动态生成一个唯一的哈希码并乘上n-1。
现在令i=这个值,然后以i为索引,让tab[i]给p赋值,并判定tab[i]是否等于null。
对与一个节点来说,这换代码是判断这个节点里面是否存贮有数据。
显然,这是我们第一次输入,节点里面肯定没有任何数据,
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
继续执行,tab[i]被赋值,我们看到被一个一个新节点方法赋值,这其中newNode 的方法代码为
Node newNode(int hash, K key, V value, Node next) {
return new Node<>(hash, key, value, next);
}
显然。null是对next的赋值,next,顾名思义是下一个,在这里,下一个节点的里面的数据被规定为null。
后面庞大的else代码块可以直接略过。
return null!
即putVal方法返回null!
即put方法返回null!
即map.put(e, PRESENT)==null成立为true!
即add方法返回true!
添加成功!
我们意识到,HashSet本质上还是HashMap,而HashMap本质上还是Node对象数组。【Node是节点。】
package add;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
HashSet
但是第二次添加name输出结果却为false,我们知道这是由于传入的key值实际上是HashMap的key值,而key值不允许重复所以添加失败。现在我们来详细分析代码。
现在直接对最重要的putVal进行分析——
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node 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)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;
}
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;
}
第一行if语句显然成立,因为这是又一次调用方法,tab和table都刷新了。
但是对于第二个if代码而言。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
因为添加的对象一样,所生成的hash码也一样,即,tab[i]的位置和第一次一样,这个时候tab[i]的位置中以及有第一次赋的数据了,因此不为null!执行else语句。
Node e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
我们来看else的代码块中第一个嵌套的if语句。
首先定义了节点e和key值k
首先要判定p.hash=hash。
hash我们知道,是putVal方法里面输入的值,本质上是传入的key值生成的哈希码,p.hash是什么?我们点进去看看——
static class Node implements Map.Entry {
final int hash;
final K key;
V value;
Node next;
出现一个节点的方法,我截取了一部分,可以看到,这个hash是该静态方法定义的final修饰的全局变量。
p是节点,调用hash,尽管第二个if判断语句不是true,但是在判断语句之中给p赋值的行为依然有效!
因此,tab[i]还是赋值给了p,这个时候,tab[i]中的hash ,key value也被赋值了,且等于第一次用add方法时,用户赋的值。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
那么这个判断就很好理解了,就是判断p中的属性,到底等不等于第一次用户赋的值,对于我们重复添加相同对象这一行为,显然是等于的。(其他原因因为个人所学水平原因,暂时不足以讨论,我们只讨论第二次相同赋值。)
转进到这个if判断语句
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
e=p,值为第一次赋的值,显然不为null。
在方法之中我们又定义了value类型的值oldValue并把value赋给它(其实就是add方法里面那个常量)
然后下面是if判断
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
如果因为传入的时候onlyIfAbsent就被规定为false,所以!onlyIfAbsent为true,下面的代码一定会执行!
返回oldValue,其实就是第一次被赋的值,也就是add方法里面那个常量!
即putVal方法返回oldValue!
即put方法返回oldValue!
即map.put(e, PRESENT)==null不成立为false!
即add方法返回false!
添加失败!