JAVA集合:常見Set原始碼學習

Danielxf發表於2019-03-01

Set也是Java集合中重要的一部分,我們常見的Set有HashSet,LinkedHashSet,TreeSet,雖然平時可能不是很常用,但是基礎還要有必要學習一下的。(以下原始碼來自jdk1.8.0_20)

HashSet

HashSet其實很簡單,原始碼量也比較少,學習過HashMap之後基本上看一遍就懂了,關於HashMap可以看一下之前的文章【HashMap原始碼深度解析】

繼承結構

HashSet繼承了HashSet,實現了Set介面以及Cloneable, java.io.Serializable介面。

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
複製程式碼

成員變數

  • HashSet其實是使用HashMap的key集合來實現的,所以有個HashMap成員變數

private transient HashMap<E,Object> map;

  • map中的value值,set只需要使用到key集合,所以value使用new Object即可

private static final Object PRESENT = new Object();

建構函式

從HashSet的構造引數其實就是初始化了一個HashMap。

最後一個建構函式不是public宣告的,而是default(同包訪問許可權),所以我們無法使用,這個建構函式是為了給LinkedHashSet重寫用的,第三個引數其實沒有作用,而是直接呼叫了LinkedHashMap的兩個引數的建構函式,預設以插入順序排序

    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);
    }
複製程式碼

主要方法

  • add方法,增加一個元素,呼叫的HashMap的put方法
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
複製程式碼
  • contains方法,判斷元素是否存在,呼叫的HashMap的containsKey方法
    public boolean contains(Object o) {
        return map.containsKey(o);
    }
複製程式碼
  • remove方法,刪除一個元素,呼叫的HashMap的remove方法,返回值和PRESENT比較。(如果不存在則返回null和PRESENT比較返回false)
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }
複製程式碼
  • size方法,返回set大小,呼叫的HashMap的size方法
    public int size() {
        return map.size();
    }
複製程式碼
  • isEmpty方法,判斷set是否為空,呼叫HashMap的isEmpty方法
    public boolean isEmpty() {
        return map.isEmpty();
    }
複製程式碼
  • clear方法,清空set中的元素,呼叫HashMap的clear方法
    public void clear() {
        map.clear();
    }
複製程式碼
  • clone方法,複製set,先呼叫父類Object的clone方法,再呼叫HashMap的clone方法複製map
    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);
        }
    }
複製程式碼
  • iterator迭代器,返回的是map的key集合的迭代器
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }
複製程式碼

LinkedHashSet

LinkedHashSet更加簡單,繼承了HashSet,建構函式呼叫了上面HashSet的最後一個建構函式,用LinkedHashMap實現,其他主要方法繼承自HashSet,沒有單獨實現。

繼承關係

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
複製程式碼

建構函式

建構函式都是呼叫父類HashSet的構造方法,使用LinkedHashMap的key值集合來實現。

    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }
    
    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }
    
    public LinkedHashSet() {
        super(16, .75f, true);
    }
    
    public LinkedHashSet(Collection<? extends E> c) {
        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
    }
複製程式碼

TreeSet

從上面兩種set可以看出來set其實就是用對應的map的key值集合來實現的,TreeSet對應的就是有序Map了,預設是TreeMap。關於TreeMap的構造,增加刪除元素之後調整的原理可以參考JAVA集合:TreeMap紅黑樹深度解析

繼承關係

  • TreeSet繼承了AbstractSet類,並且實現了NavigableSet介面以及Cloneable, java.io.Serializable介面,NavigableSet介面繼承了SortedSet介面。
public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
複製程式碼

成員變數

  • 有序map集合,NavigableMap是SortedMap的子類

private transient NavigableMap<E,Object> m;

  • map的value值

private static final Object PRESENT = new Object();

建構函式

從建構函式可以看出來TreeSet其實是用有序Map來實現的,預設使用TreeMap。

    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
    
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
    
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }
    
    public TreeSet(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    
    public TreeSet(SortedSet<E> s) {
        this(s.comparator());
        addAll(s);
    }
複製程式碼

常用方法

  • add方法,增加一個元素,直接呼叫TreeMap的put方法,e作為key值,value值為PRESENT。
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
複製程式碼
  • addAll方法,把集合中的元素插入到set中
    public  boolean addAll(Collection<? extends E> c) {
        // Use linear-time version if applicable
        if (m.size()==0 && c.size() > 0 &&
            c instanceof SortedSet &&
            m instanceof TreeMap) {
            SortedSet<? extends E> set = (SortedSet<? extends E>) c;
            TreeMap<E,Object> map = (TreeMap<E, Object>) m;
            Comparator<?> cc = set.comparator();
            Comparator<? super E> mc = map.comparator();
            if (cc==mc || (cc != null && cc.equals(mc))) {
                map.addAllForTreeSet(set, PRESENT);
                return true;
            }
        }
        return super.addAll(c);
    }
複製程式碼
  • first方法,獲取第一個(最小的)元素,直接呼叫Map的firstKey,返回最左邊的元素
    public E first() {
        return m.firstKey();
    }
複製程式碼
  • last方法,獲取最後一個(最大的)元素,呼叫Map的lastKey方法,返回最右邊的元素
    public E last() {
        return m.lastKey();
    }
複製程式碼
  • floor方法,獲取小於等於給定元素的最大元素,呼叫Map的floorKey方法
    public E floor(E e) {
        return m.floorKey(e);
    }
複製程式碼

以預設的TreeMap為例

    public K floorKey(K key) {
        return keyOrNull(getFloorEntry(key));
    }
    
    final Entry<K,V> getFloorEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {     //根節點不為null,進入迴圈,否則直接返回null
            int cmp = compare(key, p.key);
            if (cmp > 0) {      //如果key大於當前節點
                if (p.right != null)    //如果right不為null,說明有更大的節點,繼續迴圈right節點
                    p = p.right;
                else                    //否則說明當前節點最大,並且小於key,直接返回
                    return p;
            } else if (cmp < 0) {   //如果key小於當前節點,則查詢左子樹
                if (p.left != null) {   //如果left不為null,繼續迴圈left
                    p = p.left;
                } else {                //否則查詢比當前節點更小的節點(向上查詢)
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.left) {   //如果當前節點是左孩子,說明父節點較大,繼續向上查詢
                        ch = parent;
                        parent = parent.parent;
                    }   //直到節點是右孩子,此時的父節點比當前節點小,繼續迴圈父節點
                    return parent;
                }
            } else      //如果相等,直接返回
                return p;

        }
        return null;
    }
複製程式碼
  • ceiling方法,獲取大於給定元素的最小元素,呼叫Map的getCeilingEntry方法
    public E ceiling(E e) {
        return m.ceilingKey(e);
    }
複製程式碼

以TreeMap為例,此方法和上面的getFloorEntry方法正好相反

    public K ceilingKey(K key) {
        return keyOrNull(getCeilingEntry(key));
    }
    
    final Entry<K,V> getCeilingEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = compare(key, p.key);
            if (cmp < 0) {
                if (p.left != null)
                    p = p.left;
                else
                    return p;
            } else if (cmp > 0) {
                if (p.right != null) {
                    p = p.right;
                } else {
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.right) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;
                }
            } else
                return p;
        }
        return null;
    }
複製程式碼
  • higher方法,獲取大於給定元素的最小元素,此方法和getCeilingEntry方法一樣
    public E higher(E e) {
        return m.higherKey(e);
    }
複製程式碼
  • lower方法,返回小於給定元素的最大元素,呼叫Map的getLowerEntry方法,和getFloorEntry方法一樣
    public E lower(E e) {
        return m.lowerKey(e);
    }
複製程式碼
  • pollFirst方法,返回第一個元素並刪除,呼叫Map的pollFirstEntry方法
    public E pollFirst() {
        Map.Entry<E,?> e = m.pollFirstEntry();
        return (e == null) ? null : e.getKey();
    }
複製程式碼

以TreeMap為例,獲取第一個元素,如果不為null,則刪除,如果為null,則新建一個entry返回

    public Map.Entry<K,V> pollFirstEntry() {
        Entry<K,V> p = getFirstEntry();
        Map.Entry<K,V> result = exportEntry(p);
        if (p != null)
            deleteEntry(p);
        return result;
    }
    
    static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
        return (e == null) ? null :
            new AbstractMap.SimpleImmutableEntry<>(e);
    }
複製程式碼
  • pollLast方法,返回最後一個元素並刪除,和pollFirst對應
    public E pollLast() {
        Map.Entry<E,?> e = m.pollLastEntry();
        return (e == null) ? null : e.getKey();
    }
複製程式碼
  • contains方法,判斷是否含有給定的元素,呼叫Map的containsKey方法
    public boolean contains(Object o) {
        return m.containsKey(o);
    }
複製程式碼
  • remove方法,刪除給定的元素,呼叫Map的remove方法
    public boolean remove(Object o) {
        return m.remove(o)==PRESENT;
    }
複製程式碼
  • clear方法,清空set中的元素,呼叫Map的clear方法
    public void clear() {
        m.clear();
    }
複製程式碼
  • isEmpty方法,判斷set是否為空,呼叫Map的isEmpty方法
    public boolean isEmpty() {
        return m.isEmpty();
    }
複製程式碼
  • iterator迭代器,返回Map的key值集合的迭代器
    public Iterator<E> iterator() {
        return m.navigableKeySet().iterator();
    }
複製程式碼
  • subSet,獲取子set
    public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
                                  E toElement,   boolean toInclusive) {
        return new TreeSet<>(m.subMap(fromElement, fromInclusive,
                                       toElement,   toInclusive));
    }
複製程式碼

總結

  • HashSet其實是使用HashMap的key值集合來實現的,所以不能有重複的元素。
  • LinkedHashSet繼承了HashSet,用LinkedHashMap的key值集合來實現其功能,所以不能有重複元素,並且以插入元素的順序排序。
  • TreeSet是使用有序的Map來實現的,預設使用的是TreeMap。

Set其實就是Map的key集合,用Map來實現Set,所以掌握了Map基本上就學會了Set。

相關文章