前面我们分析了equals和contains方法的底层代码,接下来分析add底层代码。
看一段代码。
情况一:
package blog;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("Tom");
set.add("Tom");
System.out.println(set.size());//输出1,为什么
}
}
HashSet<String> set = new HashSet<>();先创建一个HashSet对象,调用了该类中无参构造方法,执行了该构造方法中map = new HashMap<>();其中map为HashSet全局变量
set.add("Tom");调用HashSet 中add方法,如下图。map为全局变量,指向的是创建HashSet对象时创建的HashMap对象
我们前面说过,set中添加信息时,不允许重复,那么add是怎么不允许重复的呢?接下来看它的底层代码。
ctrl键+点击add得到上图代码,也就是HashSet 中add方法,此方法调用了HashMap中put方法。底层代码如下
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node<K,V>[] tab; Node<K,V> 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<K,V> 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<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;
}
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;
}
底层代码分析:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true); //put方法调用了putVal方法,putVal方法里又调用了hash方法,代码如下
}
hash方法返回一个hash值,其中“(判断语句)?表达式1 :表达式2”是一个三目运算符,前面判断为真,则执行表达式1,否则表达式2,key就是添加的那个Tom,因为key==null,不成立,所以执行“:”后面的分支,hashCode也是一个方法,可以认为是返回对象地址的,比如
hashCode源代码
上面调用的的是Object.class中的hashCode方法,源码为
下面调用的是String重写后的hashCode方法,(表面调用Object类中的hashCode,实际上调用String重写后的hashCode方法——多态)
重写后hashCode代码
因为hash初值相同(默认为0),这里的value就是Tom,Tom相同,length就相同,字符数组val[]中各元素对应相等,对应的ASCII值也就相同,所以得到的h值就相同。所以相同内容得到相同结果,不同内容得到不同结果。
但注意:name1与name2地址并不相同,只是他们的hashCode相同
总结:不同对象的hashCode可能不相同,在Object类中,不同对象的hashCode一定不同,但在String类中,只要字符串内容相同,则hashCode相同,要看调用的是那个类里的hashCode(注意观察是否存在多态);相同对象的HashCode的值一定相同。所以HashMap中hash方法:传入相同的对象,得到相同的结果。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)//table是一个无参的数组,初始默认值为空null,所以(tab=table)==null为true((tab=table)==null清空tab,使tab为null)
n = (tab = resize()).length;//resize()也是hashMap中一个方法,可以点进去看一看,其中有一行是table=newTab,如图
表明table和tab指的是同一个地址——newTab
最后返回一个newTab,所以table与tab都指向newTab,长度默认为16,即n=16,如图
if ((p = tab[i = (n - 1) & hash]) == null)//15与上面得到的hash值进行一个按位与运算得到一个 i 值(不一定是0或1),将tab[i](tab与table指向同一个地址,所以table[i]也是有值的,且相同)赋给了p,因为最初数组并没有元素,所以是空的,所以p==null为true,执行下面语句
tab[i] = newNode(hash, key, value, null);//开辟一个新结点newNode,返回key值,即tab[i]中存入了Tom。然后直接到最后,返回了null
else {
Node<K,V> 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<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;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}