Set 接口是 Java Collections Framework 中的一員,它的特色是:不能包含重複的元素,容許且最多隻有一個 null 元素。Java 中有三個經常使用的 Set 實現類:html
JDK 在實現時,這 3 個 Set 集合的核心功能其實分別委託給了: HashMap, LinkedHashMap 和 TreeMap,關於這 3 個 Map 的源碼分析可查看本站發佈的其餘文章。java
接下來對這 3 個 Set 集合的源碼簡單分析,並解決一些面試可能會遇到的問題。面試
若是去除註釋,HashSet 源碼也就 200 行左右,除了序列化和克隆的方法,代碼以下:數組
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { // 實際存儲元素的對象 private transient HashMap<E,Object> map; // 存儲在 HashMap 中全部 key 的共享的 value 值 private static final Object PRESENT = new Object(); // 空構造函數 public HashSet() { map = new HashMap<>(); // 0.75f 加載因子 } // 使用已有集合填充並初始化 public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } // 指定關聯 HashMap 的初始容量和加載因子 public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } // 只指定初始容量 public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } // 包訪問權限的構造方法,僅用於 LinkedHashSet 初始化 // 使用 LinkedHashMap 做爲底層存儲 HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); } // HashSet 中的元素就至關於 HashMap 中的 key public Iterator<E> iterator() { return map.keySet().iterator(); } // 如下這些方法,都是對 Set 接口中定義的方法的實現 public int size() { return map.size(); } public boolean isEmpty() { return map.isEmpty(); } public boolean contains(Object o) { return map.containsKey(o); } // 全部鍵值對的 value 值都是 PRESENT 這個 Object 對象 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(); } // JDK 8 提供的一種並行遍歷機制 - 可分割迭代器 public Spliterator<E> spliterator() { return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0); } }
能夠看到,底層使用 HashMap 用於實際存放數據,而 PRESENT 就是全部寫入 map 的 value 值。實現比較簡單,核心功能都委託給了 HashMap。緩存
不論是 Set 仍是 Map,存儲的都是對象,在 Java 中,判斷兩個對象是否相等,都是經過 equals 和 hashCode 兩個方法:安全
因此,HashSet 存儲的對象,都要正確覆蓋實現 equals 和 hashCode 兩個方法。併發
其實,HashSet 中的元素其實就是 HashMap 的 key,在插入時:ide
關於迭代器,就是利用的 HashMap 中的 KeyIterator。函數
LinkedHashSet 的代碼就更簡單了,它繼承自 HashSet,代碼以下:源碼分析
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable { // 調用父類特定的構造方法,初始一個 LinkedHashMap 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); } @Override public Spliterator<E> spliterator() { return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED); } }
所有代碼就這些,值得注意的是構造方法中的 super 調用的是 HashSet 中的一個默認包訪問權限的構造方法,核心功能都委託給了 LinkedHashMap。
像 HashSet 那樣,它能在常量時間內完成集合的基本操做 add, contains 和 remove。性能略低於 HashSet,由於要額外維護一個鏈表。但有一個例外,在遍歷時,LinkedHashSet 花費的時間與元素個數成比例,而 HashSet 花費時間較多,由於它與集合容量成比例。
TreeSet 是一個有序的 Set 集合,元素大小比較方式能夠是天然順序,也能夠指定一個 Comparator 比較器。
它是對 TreeMap 的封裝,提供了在有序集合上的遍歷 API 好比,lower、floor、ceiling 和 higher 分別返回小於、小於等於、大於等於、大於給定元素的元素。能在 log(n) 時間內完成集合的基本操做 add, contains 和 remove。
有一點能夠了解下,Set 接口定義的是使用 equals 方法比較元素是否相等,而 TreeSet 使用則是 compareTo 或者 compare 方法進行比較,這知足集合的行爲,只不過沒有遵照 Set 接口的規範。
TreeSet 源碼也比較簡單,畢竟只是對 TreeMap 封裝了一下,這裏再也不貼出。
以前分析了一部分經常使用集合的源碼,這些集合都各有各的特色,它們的區別也常常出如今面試中,本文最後就對常見的面試題進行下總結。
fast-fail,即快速失敗,在遍歷集合的過程當中,若是發現集合結構發生了變化,會拋出 ConcurrentModificationException 運行時異常。
注意,在不一樣步修改的狀況下,它不能保證會發生,它只是盡力檢測併發修改的錯誤。
原理是經過一個 modCount 字段來實現的,這個字段記錄了列表結構的修改次數,當調用 iterator() 返回迭代器時,會緩存 modCount 當前的值,若是這個值發生了不指望的變化,那麼就會在 next, remove 操做中拋出異常。
本文以及以前介紹的集合都是常規的,經常使用的,非線程安全的集合實現,接下來將會介紹 Java 併發包下的線程安全的集合,以及一些有特殊用途的集合實現。
原文出處:https://www.cnblogs.com/wskwbog/p/11260056.html