看过HashMap的源码之后,在看HashSet集合简直是so Easy,仅仅从代码的数量上就可以看出来了。
这一篇的分析顺序是Set,AbstractSet,HashSet。
Set感觉没有什么可分析的。
public interface Set<E> extends Collection<E> {//Set接口继承了Collection接口 int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean retainAll(Collection<?> c); boolean removeAll(Collection<?> c); void clear(); boolean equals(Object o); int hashCode(); @Override default Spliterator<E> spliterator() { return Spliterators.spliterator(this, Spliterator.DISTINCT); } }这是AbstractSet类的源代码,是抽象的。
/* 继承AbstractCollection,实现Set */ public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> { protected AbstractSet() { } /* 比较,都是这一种,容器的比较方式都是一样的,1.判断是否是本对象 2.判断类型 3.判断长度 4.遍历,一个一个比较 */ public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Set)) return false; Collection<?> c = (Collection<?>) o; if (c.size() != size()) return false; try { return containsAll(c); } catch (ClassCastException unused) { return false; } catch (NullPointerException unused) { return false; } } /* 前面也说过了,容器的hash值为每个元素的hash值相加 */ public int hashCode() { int h = 0; Iterator<E> i = iterator(); while (i.hasNext()) { E obj = i.next(); if (obj != null) h += obj.hashCode(); } return h; } /* 删除指定的集合中所包含的所有元素 */ public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c);//判断该对象是否为空 boolean modified = false; if (size() > c.size()) { //判断两个集合的长度,遍历的是长度短的集合,很厉害,以前从来没有想到过,这样可以提高效率 for (Iterator<?> i = c.iterator(); i.hasNext(); ) modified |= remove(i.next());//直接删除,因为remove()方法中会判断,包含该节点直接删除,不包含,则不会 } else { for (Iterator<?> i = iterator(); i.hasNext(); ) { if (c.contains(i.next())) { i.remove(); modified = true; } } } return modified; } }
HashSet底层是一个Map集合,HashSet的存储只用到了key值部分。
先看一下HashSet集合的定义和成员变量。
/* HashSet底层就是一个HashMap,HashSet只是用了HashMap的key,value值并未使用,存入的都是一个Object类型对象的引用 */ public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map;//HshSet底层是一个HashMap, private static final Object PRESENT = new Object();//present
再看一下HashSet的构造方法(有四个)。HasHMap集合的构造方法也是四个。这是凑效吗?其实不是的,因为其底层是HashMap集合,也就是其实就是在HashSet集合的构造方法中创建一个HashMap对象。HashMap的创建方式有四种,可以看一下源码。就是根据HashMap衍生出来的。
/* HashSet(Collection<? extends E> c),HashSet(int initialCapacity, float loadFactor),HashSet(int initialCapacity) 这三个构造方法,其实就是根据HashMap的构造方法衍生出来的, */ public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } /* 这个构造方法,修饰符是默认的,只能在该包下访问,底层是一个LinkedHashMap 就是这个构造方法,其子类的LinkedHashset的构造方法都是调用的这个构造方法 */ HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
其实还有一个构造方法,他的修饰符是默认的,创建LinkedHashSet集合时,一定会调用这个构造方法。
/* 这个构造方法,修饰符是默认的,只能在该包下访问,底层是一个LinkedHashMap 就是这个构造方法,其子类的LinkedHashset的构造方法都是调用的这个构造方法 */ HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
HashSet的一众方法,均是调用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(); }HashSet集合还有两个私有方法,一个readObject()和writeObject(),都是为了实现序列化和反序列化