Set接口和常用方法
Set接口基本介绍
Set接口常用方法和遍历方式
解读
- 以Set接口的实现类HashSet讲解Set接口方法
- set接口的实现类的对象,不能放重复元素,可以添加一个null
- set接口对象存放的数据是无序的(添加和取出的顺序不一致)
- 注意:取出的顺序是固定的
遍历
- 可以使用迭代器
- 增强for
- set接口对象不能通过索引来获取
Set接口实现类-HashSet
HashSet的说明
HashSet案例说明
- 在执行add方法后,会返回一个boolean值,添加成功返回true,否则返回false
- 可以通过remove指定删除某个对象
- 对于对象来说,对象是否相同是根据equals方法判断的
- 但是对于String来说是比较特别的
- 对于一些基础类型的包装类也是如此,画横线的意思,即使不写成包装类,在add的时候它也会以对象的方式存储,即包装成类
HashSet底层机质说明
源码解读
从构造器可以看出,HashSet的底层其实是HashMap
其中PRESENT是一个final static的空Object对象,占位用的
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,类型为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() 对当前这个链表进行树化(转成红黑树)
//注意,在转成红黑树时,要进行判断,判断条件r
//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 - 1) // -1 for 1st,TREEIFY_THRESHOLD=8,链表长度到达八个
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;
}
数组默认大小为16,如果到达0.75倍就会扩容两倍
HashSet底层机质说明
说明
- 如果想要把所有元素都放在一个链表上,要保证hashcode相同了,对于对象来说,可以重写hashcode方法来保证其相同。如果某个链表长度以及到达8,再向这个链表中添加元素,就会出发扩容机质,反复如此直到数组长度到达64,再添加就会变成树结构
- 扩容,如果set内有8个元素,那么就扩容,不论这八个元素在哪
Set接口实现类-LinkedHashSet
LinkedHashSet继承了HashSet实现了Set接口
解读
- LinkedHashSet加入顺序和取出元素顺序一致
- LinkedHashSet底层维护的是一个LinkedHashMap(HashMap的子类)
- LinkedHashSet底层结构(数组+双向链表)
- 添加第一次时,直接将数组table扩容到16,存放的结点类型是 LinkedHashMap$Entry
- 数组是
HashMap$Node[]
存放的元素/数据是LinkedHashMap$Entry
类型
//继承关系是在内部类完成
static class Entry<K,V>extends HashMap.Node<K,V> {
Entry<K, V> before,after;
Entry(int hash,K key,V value,Node<K,V> next) {
super(hash,key,value,next);
}
}
源码分析
LinkedHashSet存放和取出的顺序一样,因为每一个节点都有两个指针指向前面和后面,在添加的时候指针会做出改变,但总体是一个环形的链表。