前言:
集合类型三巨头:List、Set、Map
在之前的博客中已经讲了关于List的实现类、Map的实现类,现在还有Set相关的没有介绍过了。
为什么先介绍Map后介绍Set呢?按理说Set应该比Map的实现更简单点啊?
当然,这是笔者的一点小心思,看过源码的都知道,Set是基于Map来实现的,所以想了解Set,必须先了解Map
对Map不太了解的同学可以先看一下笔者另一篇博客:https://blog.csdn.net/qq_26323323/article/details/86219905
1.HashSet结构分析
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
// 一个map作为其成员变量
private transient HashMap<E,Object> map;
// 后面会用到,每一个HashSet唯一的PRESENT
private static final Object PRESENT = new Object();
2.HashSet构造方法
// 1.空参构造
public HashSet() {
// 初始化map
map = new HashMap<>();
}
// 2.使用一个集合进行初始化加载
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
构造方法主要还是围绕创建一个HashMap
3.添加操作
// HashSet.add()
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
我们来详细分析一下这个添加方法
需要回顾一下HashMap的put方法,e作为map的key,如果map已经存在该key,则比较value(也就是当前的PRESENT),如果value也相同则执行update操作,如果不同,则将这个value添加到链表尾。
我们来分两种情况来分析这个add方法
1)之前没有添加过e
则会被顺利添加到map中,value为PRESENT,此时map.put()方法返回的是null,null==null返回的是true,则整个hashSet.add()方法返回true
2)之前已经添加过e
map.put()方法,发现key所在index已经有数据,进行value的比较操作,发现两个value也是同一个value,则执行update操作,put方法返回PRESENT,PRESENT!=null,则整个hashSet.add()方法返回false
这就是HashSet数据不重复的原因
4.删除操作
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
map.remove()方法笔者不再详述,可以参考之前的文章。
map.remove()方法返回该key(也就是Object o)所关联的value,
* 如果HashSet已经添加过该key,则value为PRESENT,整个HashSet.remove()方法返回true;
* 如果HashSet没有添加过该key,则value为null,整个HashSet.remove()方法返回false;
5.查询操作
Set集合的查询只能用来查询当前值是否存在
public boolean contains(Object o) {
return map.containsKey(o);
}
也是通过map来实现,查看Object o这个key有没有对应的value存储在Map中
6.遍历操作
关于Set,我们更多的是使用其遍历操作,那么遍历是如何实现的呢?这里我们深入一下
1)HashSet.iterator()
// 1.HashSet.iterator()
public Iterator<E> iterator() {
// 这里需要先获取keySet
return map.keySet().iterator();
}
// 2.HashMap.keySet()
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
// 主要就是KeySet对象
// 下面我们来看下创建这个类做了什么
ks = new KeySet();
keySet = ks;
}
return ks;
}
// 3.new KeySet()
// 主要就是创建这个类
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
// 我们再来看一下iterator方法
public final Iterator<K> iterator() { return new KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
// 4.new KeyIterator()
final class KeyIterator extends HashIterator
implements Iterator<K> {
// 主要就是这个方法nextNode()
public final K next() { return nextNode().key; }
}
2)new KeyIterator()
我们来看一下在创建KeyIterator对象的时候发生了什么
// final class KeyIterator extends HashIterator
// keyIterator没有自己的构造方法,则其在构造时会调用父类HashIterator的空参构造方法
// HashIterator
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
// 就是这个方法
HashIterator() {
expectedModCount = modCount;
// 将HashMap的table赋值到t
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
// 初始化第一个Entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
3)KeyIterator.nextNode()分析
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
//
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
如何理解这种遍历方式呢?我们知道HashMap是数组+(链表|红黑树)的方式。
遍历元素,首先从数组下标为0的节点开始遍历,获取元素e,然后获取e.next,...一直获取到e.next.next...==null为止,说明数组下标为0的链表上所有元素都已经被获取到了。
然后从数组下标为1的节点开始遍历,继续重复上述动作