前言 在前面几个章节中,已经对集合中的 List
列表一支进行了相关的学习。在我们日常编程中,使用最多的集合还有 Set
。本章我们将学习Set的相关知识。
1 2 3 4 5 6 graph TD A[Christmas] -->|Get money| B(Go shopping) B --> C{Let me think} C -->|One| D[Laptop] C -->|Two| E[iPhone] C -->|Three| F[Car]
java基础集合框架
HashSet源码学习 在上图中,我们可以看到,Collection接口
的另一分支:Set
集合,常用的有:HashSet,TreeSet等。在此,我们先学习一下平时最常用的 HashSet。
数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class HashSet <E > extends AbstractSet <E > implements Set <E >, Cloneable , java .io .Serializable { private transient HashMap<E,Object> map; private static final Object PRESENT = new Object(); }
从 HashSet 的源码中,我们可以看出,该类继承了 AbstractSet抽象类
,并实现了 Set接口
。在其内部,维持了一个 HashMap
类型的对象。由此可知,HashSet的内部是通过HashMap类型对象来实现具体操作的。
Set接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public interface Set <E > extends Collection <E > { boolean add (E e) ; boolean addAll (Collection<? extends E> c) ; boolean remove (Object o) ; boolean removeAll (Collection<?> c) ; void clear () ; boolean retainAll (Collection<?> c) ; int size () ; boolean isEmpty () ; boolean contains (Object o) ; Object[] toArray(); <T> T[] toArray(T[] a); boolean containsAll (Collection<?> c) ; boolean equals (Object o) ; int hashCode () ; Iterator<E> iterator () ; }
在 Set接口
中,定义了Set集合的相关方法。具体实现需由子类进行完成。
AbstractSet源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public abstract class AbstractSet <E > extends AbstractCollection <E > implements Set <E > { protected AbstractSet () { } 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 ; } } 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()); } else { for (Iterator<?> i = iterator(); i.hasNext(); ) { if (c.contains(i.next())) { i.remove(); modified = true ; } } } return modified; } }
在抽象类 AbstractSet 中,只是实现了equals
, hashCode
, removeAll
这三个方法。内部最终还是会调用父类AbstractCollection的相关方法。
构造方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public HashSet () { map = new 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); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
HashSet 类中有5个不同的构造函数。其中最上面4个,内部创建了 HashMap
类型的对象;而最后一个构造函数,则构建了一个 LinkedHashMap
类型的对象。所以最后一个对象在构造LinkedHashSet对象时会用到。下文会分析到。由于HashSet内部维持了一个 HashMap 类型的对象,且各种操作都是通过这个内部对象实现的。所以,HashSet构造函数的主要作用是创建 HashMap对象。关于 Map 的相关子类源码,我们下篇文章接着分析。在此处只要知道HashSet内部操作都是围绕着HashMap进行的。各种构造函数,也是为了初始化HashMap的大小和设置HashMap的加载因子。
基本方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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(); } public Object clone () { try { HashSet<E> newSet = (HashSet<E>) super .clone(); newSet.map = (HashMap<E, Object>) map.clone(); return newSet; } catch (CloneNotSupportedException e) { throw new InternalError(e); } } public int size () { return map.size(); } public boolean isEmpty () { return map.isEmpty(); } public boolean contains (Object o) { return map.containsKey(o); }
可见,HashMap 的相关操作都是通过 HashMap对象实现的。
关于 Set
集合的其他子类,在此不做详细讲述,因为实现逻辑和 HashMap 大致一样,只不过 LinkedHashSet内部维持一个 LinkedHashMap,TreeSet内部维持的是一个TreeMap 。
LinkedHashSet源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class LinkedHashSet <E >extends HashSet <E >implements Set <E >, Cloneable , java .io .Serializable { public LinkedHashSet (int initialCapacity, float loadFactor) { super (initialCapacity, loadFactor, true ); } public LinkedHashSet (int initialCapacity) { super (initialCapacity, .75f , true ); } public LinkedHashSet () { super (16 , .75f , true ); } }
TreeSet源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class TreeSet <E > extends AbstractSet <E > implements NavigableSet <E >, Cloneable , java .io .Serializable { private transient NavigableMap<E,Object> m; public TreeSet () { this (new TreeMap<E,Object>()); } TreeSet(NavigableMap<E,Object> m) { this .m = m; } }