HashSet可以存放null值,但是只能有一个null,HashSet不保证元素是有序的,这个顺序取决于hash后,在确定索引的位置,它不能有重复元素/对象。 原因: HashSet是Set接口实现类,而HashSet实际上是HashMap(以下源码可以证明):
我们可以看到HashSet构造器引用了HashMap对象 而HashMap底层是:数组+链表+红黑树(这里不展开谈)
那么,HashSet添加元素,底层是怎么执行的呢?断点调试追一下源码分析一下。
1.首先进去调用了构造器
- 执行add()方法
3.执行put()方法
这里有个hash()方法,那么这个hash方法是什么呢?继续追进去看一下源码。
可以看到这个hash()方法内部,其实是返回key的hash值,但是不完全等价于hashcode,而是进行了一个无符号右移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[]
//if语句 表示如果当前table 是null,或者大小为0,那么就进行第一次扩容
//第一次扩容的大小是16个空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;//这个resize()方法就是对HashSet进行扩容。
//1.根据Key得到hash 去计算 该Key应该存放到table表的哪个索引位置并把这个位置的对象,赋给p
//2.怎么判断p是否为null
1)如果p为null,表示还没存放元素,就创建一个Node
2)就放在该位置tab【i】 = newNode(hash,key,value,null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果满足下面条件的任意一个就不能加入HashSet:
//1.准备加入的key和p都指向了Node 节点的Key是同一个对象
//2. p指向的Node 结对是key 的equals() 和准备的key比较后相同
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 {
//如果table对应的索引位置,已经是一个链表,就是用for循环比较
//(1)依次和该链表的各个元素作比较,若都不同,则加入到该链表的最后
// 注意再把元素添加到链表后,立即判断 该链表是否已经达到8个结点
// 如果已经达到,就调用treeifyBin()对当前这个链表进行树化(转成红黑树)
// 当然进化前也要判断这个table表有没有64个容量。
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;
}`
以上就是对putVal内部源码的具体分析了。