Set接口基本介绍
读取和取出的元素无序,即不一致,不存在索引index;
不允许重复的元素(比如只能包含一个null);
Set的典型实现子类:HashSet,LinkedHashSet,TreeSet。


Set接口的类实现对象,不能存放重复的元素,但可以添加一个null值,存放的元素是无序的,但却是固定的。
--------------------------------
HashSet实现类,底层实际上是HashMap
HashMap底层的存储机制是一个数组和链表的组合,添加一个元素时,会先得到对应的hash值,在转化成索引值,通过对比table表索引,查看该索引下是否存有元素,如果没有,则直接加入,有元素,就再调用equals方法比较待添加元素和已存在的元素是否相等,相等,则放弃添加,返回false,不相等,则在已存在的元素之后,按链表的形式添加进去(这时链表上的元素hash值相等)。
在java8中,如果一个链表的元素个数超过8,且table表大小.超过64,就会树化成一颗红黑树。

1. 执行 HashSet()
public HashSet() {
map = new HashMap<>();
}
2. 执行 add()
public boolean add(E e) {//e = "java"
return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
}
3.执行 put() , 该方法会执行 hash(key) 得到key对应的hash值 算法h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {//key = "java" value = PRESENT 共享
return putVal(hash(key), key, value, false, true);
}
4.执行 putVal
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;
//(1)根据key,得到hash 去计算该key应该存放到table表的哪个索引位置
//并把这个位置的对象,赋给 p
//(2)判断p 是否为null
//(2.1) 如果p 为null, 表示还没有存放元素, 就创建一个Node (key="java",value=PRESENT)
//(2.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; //
//如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
//并且满足 下面两个条件之一:
//(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() 对当前这个链表进行树化(转成红黑树)
// 注意,在转成红黑树时,要进行判断, 判断条件
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
// 如果上面条件成立,先table扩容.
// 只有上面条件不成立时,才进行转成红黑树
//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD(8) - 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;
//size 就是我们每加入一个结点Node(k,v,h,next), size++
if (++size > threshold)
resize();//扩容
afterNodeInsertion(evict);
return null;
}
*/
HashSet的扩容机制

到了临界值12,指的是table数组和其链表上有12个元素,不是在table数组上存放了12个
---------------------------
LinkedHashSet实现了Set接口,继承了HashSet类,底层是LinkedHashMap(是HashMap的子类),维护了一个数组和双向链表,通过链表维护元素次序,即遍历时的顺序和添加时的顺序一样
不允许添加重复的元素

维护的双向链表为 "AA"<->456<->"刘"<->"HSP"
第一次添加元素时,table数组直接扩容到16,存放的结点类型是Entry,Entry是继承Node类的一个静态内部类,其中拥有before和after属性,用于维护双向链表
本文介绍了Set接口的基本概念,重点剖析了HashSet、LinkedHashSet的内部实现,包括元素的无序性、不允许重复、使用HashMap作为底层存储以及它们的扩容策略,特别提到了LinkedHashSet维护元素顺序的特点。


被折叠的 条评论
为什么被折叠?



