目录
第二次加入元素(直接赋值创建的String类对象 且相同字符):
第二次加入元素(用new关键字创建String类对象 且相同字符):
一、引入
HashSet中add方法不像ArrayList中的contains方法那么简单,是一个复杂的方法体系,因此在直接研究之前先声明在其中调用的方法(本篇只讲解部分部分调用的方法:
HashSet.add()方法
HashMap.put()方法
HashMap.putVal()方法
HashMap.hash()方法
Object.hashCode方法
String.hashCode()方法
HashMap.resize()方法
二、底层代码逻辑分析
(1)HashSet.add()方法
public boolean add(E e) { //HashSet.add()方法
return map.put(e, PRESENT)==null;
}
首先我们看 HashSet.add()方法,在我之前的博客Java中的HashSet的常用方法总结中提到过,这里的map是一个全局变量,而利用HashSet无参构造方法时实际上是创建了一个HashMap对象赋给了该全局变量map,所以这里map前省略“ this. ”,因为this指代调用该方法的HashSet对象(this用法详见我的博客详述Java中的局部变量与全局变量),是调用该方法的HashSet对象在调用map全局变量。那么因为全局变量map中存的为一个HashMap对象,所以这里实际上调用了HashMap类的HashMap.put()方法,那么问题来了,HashMap类集合对象添加元素应该是两种泛型,这里为什么只传一个却也能调用HashMap.put()方法?不难发现常量PRESENT,在HashSet底层代码中找到它:
它是一个定义在HashSet类中的静态常量,那我们就了解了,HashSet类中定义的静态常量自动填补了HashMap类集合对象中value参数的空缺(实际上这里“ PRESENT ”前也省略“this.”,同样代指调用该方法的HashSet对象在调用该全局静态常量(属性)) 。接着我们退回来,发现HashSet.add方法的返回值为布尔型表达式,那这个表达式判断的是HashMap.put()方法的返回值是否为空,为空返回true,不为空返回false,但因为该返回结果决定了添加是否成功,所以到这里我们可以暂时性的得出一个结论:若HashMap.put()方法返回值为空,则添加成功。那么是什么决定着HashMap.put()方法的返回值呢?我们来看HashMap.put()方法的底层代码:
(2)HashMap.put()方法
public V put(K key, V value) { //HashMap.put()方法
return putVal(hash(key), key, value, false, true);
}
这里问题来了,HashMap.put()方法的代码更简单,直接能看出来HashMap.putVal()方法的返回值决定了HashMap.put()方法的返回值是否为空,那这里又得到一个暂时性的结论:若HashMap.putVal()方法返回值为空,则添加成功。这样就要分析HashMap.putVal()方法的底层代码了,不过我们不妨先看看该方法中传入的参数,毕竟这些参数直接决定了该方法返回结果。这里看到,后三个参数都都是直接传入,而且后两个为布尔型(这两个布尔型下文再讲),只有第一个参数是某个方法的返回值,且第二个参数决定了该方法的返回值,这是个什么方法呢?,不妨查看它的底层代码:
(3)HashMap.hash()方法
static final int hash(Object key) { //HashMap.putVal()方法
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这是HashMap.hash()方法,一看,这个方法的返回结果中不仅用到了三位运算符,位移运算符还调用了一个hashCode()方法,不管怎样,我们知道:经过一系列复杂运算,HashMap.hash()方法返回的一定是一个数值。我们再来看hashCode的底层代码:
(4)Object.hashCode方法
public native int hashCode(); //Object.hashCode方法
一看,首先,该方法是定义在Object类的,再看,这个方法没有方法体!为什么?没有方法体不应该是抽象方法吗?为什么不加abstract修饰,如果是抽象方法,那么Object类不就是抽象类了吗?但Object不是抽象类啊。实际上这个Object.hashCode()方法并不是用Java语言编写的方法,而是用C语言编写的方法,位于Java的最底层,主要是依据变量在内存中分配的地址计算出一个数。也就是说对于Object.hashCode()方法,地址不同,则返回值不同。从而说明地址不同,hash()方法返回值不同(记住这条结论!)
那么一旦解决了前面的问题,我们就正式进入核心代码——HashMap.putVal()方法的底层代码:
(由于下面代码较多,所以部分分析我直接写在注释里)
(5)HashMap.putVal()方法
第一次加元素:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) { //HashMap.putVal()方法
Node<K,V>[] tab; Node<K,V> p; int n, i;//这一行首先定义了四个局部变量,两个个数组(tab和p),两个数(n和i)
if ((tab = table) == null || (n = tab.length) == 0) //标记1
n = (tab = resize()).length; //这里将resize()方法的返回值(实际上是跟tab同类的数组)赋给了tab然后将该“返回值”的长度赋给n 这里对resize()方法做 标记2 经过这行代码则tab与table指向同一块内存区,修改tab,则table也被修改
if ((p = tab[i = (n - 1) & hash]) == null) //前面提到过hash局部变量中传入的为hash()方法的返回值,是一个数,然后这个数1与(n-1)进行按位与运算