今天面试遇到一个很有意思的问题,先说一下这个问题的背景,面试的时候被问到有两个String对象,分别是String a="abc"和String b=new String(“abc”),问这两个对象同时存进HashSet吗?了解这个问题之前我们先看看HashSet的源码。
HashSet源码解析
话不多说,先看代码。
public HashSet() {
map = new HashMap<>();
}
首先是HashSet的构造方法,我们可以看到HashSet在构造方法里调用了一下HashMap的构造方法。欸????等等!HashMap???
我们再往下继续看其他函数的源码。
public Iterator<E> iterator() {
return map.keySet().iterator();
}
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public void clear() {
map.clear();
}
注意看add方法,好家伙,原来HashSet底层就是直接定义了一个HashMap的对象,要存的东西都直接以HashMap键的形式往里面存啊!
好的,那我们接下来就来扒一扒HashMap键的原理。
HashMap源码解析
下面的代码是HashMap的put方法的源码,看过前面博客的同学们应该很了解这个过程了。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
我们看一下这个hash方法的源码是什么样子的。首先判断这个key值是否为空,是的话就直接返回0,否则返回key值的hashcode与hashcode无符号右移16位的异或结果。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
懂了,两个对象如果hashCode相同的话那么通过这个hash方法是可以得到相同的hash值。HashMap的部分完了吗?没有!我们先回顾一下HashMap插入数据的内容然后再分析String的hashCode是怎么得到的。
我们把目光放到putVal函数的这部分代码:
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
相信大家已经很熟悉了,就是HasMap的数组里的hash值和当前插入的节点的hash值进行对比,一样的话说明发生了Hash冲突。然后还需要看一下数组中的节点和待插入的节点是不是完全一样或者说值完全相等(equals返回true),是的话直接替换。懂了吗?两个String对象equals一定会返回true!所以我们接下来看一下hashCode的问题。
String源码分析
下面是String的hashCode的源码,首先是h被hash赋值了,而hash默认是0,这里的value数组其实就是String的字符数组。两个String对象值一样的话,value数组必然也是一样的。后面没啥好看的了,h是0,value数组完全相等,那么经过if语句里的一通操作,返回的h值还是一样的。
private final char value[];
private int hash; // Default to 0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
我们可以得出结论:只要两个String对象的值相等,那么他们的hashCode一定是相等的。
总结
好了,前面一通分析大家应该已经知道上面问题的答案了。由于两个对象的值以及hasCode值都相等,因此存入HashMap的键也就是一样的,因此后面存入的String对象将会覆盖前面存入的String对象,而HashSet是利用HashMap的key进行存储的,自然就只能存入一个对象了。