1. Java Set
1. Java Set 重要觀點
- Java Set接口是Java Collections Framework的成員。
- Set不容許出現重複元素-----------無重複
- Set不保證集合中元素的順序---------無序
- Set容許包含值爲null的元素,但最多隻能有一個null元素。
- Set支持泛型(類型的參數化),咱們應儘量使用它。將Generics與List一塊兒使用將在運行時避免ClassCastException。
- 先去看Map,Set的實現類都是基於Map來實現的(如,HashSet是經過HashMap實現的,TreeSet是經過TreeMap實現的,LinkedHashSet是經過LinkedHashMap來實現的)。
2. Java Set類圖
Java Set接口擴展了Collection接口。Collection接口 externs Iterable接口。java
一些最經常使用的Set實現類是HashSet,LinkedHashSet,TreeSet,SortedSet,CopyOnWriteArraySet。數組
AbstractSet提供了Set接口的骨幹實現,以減小實現List的工做量。安全

3. Java Set 方法
boolean add(E e) //若是 set 中還沒有存在指定的元素,則添加此元素(可選操做)。
boolean addAll(Collection<? extends E> c) //若是 set 中沒有指定 collection 中的全部元素,則將其添加到此 set 中(可選操做)。
void clear() //移除此 set 中的全部元素(可選操做)。
boolean contains(Object o) //若是 set 包含指定的元素,則返回 true。
boolean containsAll(Collection<?> c) //若是此 set 包含指定 collection 的全部元素,則返回 true。
boolean equals(Object o) //比較指定對象與此 set 的相等性。
int hashCode() //返回 set 的哈希碼值。
boolean isEmpty() //若是 set 不包含元素,則返回 true。
Iterator<E> iterator() //返回在此 set 中的元素上進行迭代的迭代器。
boolean remove(Object o) //若是 set 中存在指定的元素,則將其移除(可選操做)。
boolean removeAll(Collection<?> c) //移除 set 中那些包含在指定 collection 中的元素(可選操做)。
boolean retainAll(Collection<?> c) //僅保留 set 中那些包含在指定 collection 中的元素(可選操做)。
int size() //返回 set 中的元素數(其容量)。
Object[] toArray() //返回一個包含 set 中全部元素的數組。
<T> T[] toArray(T[] a) //返回一個包含此 set 中全部元素的數組;返回數組的運行時類型是指定數組的類型。
2. HashSet
1. HashSet 結構圖

HashSet,由哈希表(其實是一個 HashMap 實例)支持。它不保證 set 的迭代順序;特別是它不保證該順序恆久不變。此類容許使用 null元素。!
HashSet繼承了AbstractSet,實現了Cloneable和Serializable接口!併發
- 實現了Cloneable接口,即覆蓋了函數clone(),實現淺拷貝。
- 實現了Serializable接口,支持序列化,可以經過序列化傳輸。
2. HashSet 重要特色
- 依賴於哈希表(其實是一個 HashMap 實例)(哈希表+鏈表+紅黑樹),不能夠存儲相同元素(排重)
- 底層實現是一個HashMap(保存數據),實現Set接口。(HashSet中含有一個」HashMap類型的成員變量」map,HashSet的操做函數,實際上都是經過map實現的。)
- 非同步,線程不安全,存取速度快(同步封裝Set s = Collections.synchronizedSet(new HashSet(...));)
- 默認初始容量爲16。
- 加載因子爲0.75:即當 元素個數 超過 容量長度的0.75倍 時,進行擴容
- 擴容增量:原容量的 1 倍,如 HashSet的容量爲16,一次擴容後是容量爲32
- 重寫hashCode():HashSet集合排重時,須要判斷兩個對象是否相同,對象相同的判斷能夠經過hashCode值判斷,因此須要重寫hashCode()方法
- 重寫equals():equals()方法是Object類中的方法,表示比較兩個對象是否相等,若不重寫至關於比較對象的地址, 因此咱們能夠嘗試重寫equals方法,檢查是否排重。
- 會根據hashcode和equals來龐端是不是同一個對象,若是hashcode同樣,而且equals返回true,則是同一個對象,不能重複存放。
- fail-fast機制:HashSet經過iterator()返回的迭代器是fail-fast的。
- 兩種遍歷方法:Iterator【iterator.next()】,forEach【set.toArray();】
Set<String> set = new HashSet<String>();
set.add("first");
set.add("second");
set.add("three");
// foreach
for (String string : set) {
System.out.println(string);
}
// iterator
Iterator<String> setIterator = set.iterator();
while (setIterator.hasNext()) {
String string = (String) setIterator.next();
System.out.println(string);
}
3. TreeSet
1. TreeSet 結構圖

基於 TreeMap 的 NavigableSet 實現。使用元素的天然順序對元素進行排序,或者根據建立 set 時提供的 Comparator進行排序,具體取決於使用的構造方法。異步
TreeSet也不能存放重複對象,可是TreeSet會自動排序,若是存放的對象不能排序則會報錯,因此存放的對象必須指定排序規則。排序規則包括天然排序和客戶排序。函數
①天然排序:TreeSet要添加哪一個對象就在哪一個對象類上面實現java.lang.Comparable接口,而且重寫comparaTo()方法,返回0則表示是同一個對象,不然爲不一樣對象。性能
②客戶排序:創建一個第三方類並實現java.util.Comparator接口。並重寫方法。定義集合形式爲TreeSet ts = new TreeSet(new 第三方類());atom
TreeSet繼承了AbstractSet,實現了NavigableSet、Cloneable和Serializable接口!spa
- 繼承於AbstractSet,AbstractSet實現了equals和hashcode方法。
- 實現了NavigableSet接口,意味着它支持一系列的導航方法。好比查找與指定目標最匹配項。
- 實現了Cloneable接口,即覆蓋了函數clone(),實現淺拷貝。
- 實現了Serializable接口,支持序列化,可以經過序列化傳輸。
- TreeSet是SortedSet接口的實現類
2. TreeSet 重要特色
- 依賴於TreeMap,TreeSet是基於TreeMap實現的。(紅黑樹)複雜度爲O(log (n))
- 不能夠存儲相同元素(排重),自動排序。(有序集合)
- TreeSet中不容許使用null元素!在添加的時候若是添加null,則會拋出NullPointerException異常。
- TreeSet是非同步的方法【SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));】。
- 它的iterator 方法返回的迭代器是fail-fast的。
- TreeSet不支持快速隨機遍歷,只能經過迭代器進行遍歷! 兩種遍歷方法:Iterator【iterator.next()】,forEach【set.toArray();】
- TreeSet中的元素支持2種排序方式:天然排序 或者 根據建立TreeSet 時提供的 Comparator 進行排序。這取決於使用的構造方法。
- 天然排序,重寫compareTo方法:元素所屬的類須要實現java.lang.Comparable接口,並重寫compareTo方法。 compareTo方法除了能夠進行排序外,還有排重的功能,可是必須在compareTo方法中對類中全部的屬性值都進行判斷,不然不比較那個屬性,排重就會忽略哪一個屬性
- 定製排序,重寫compare方法:元素須要經過java.util.Comparator接口(比較器)中的compare方法進行比較大小,並排序。 compare方法除了能夠進行排序外,還有排重的功能,可是必須在compare方法中對類中全部的屬性值都進行判斷,不然不比較那個屬性,排重就會忽略哪一個屬性
ps:Comparable中的compareTo()一個參數, Comparator中compare()兩個參數,返回值都是int類型,若是返回0,表示兩個比較元素相同,若是大於0 ,前面大於後面,若是小於0,前面小於後面。線程
3. HashSet vs TreeSet
- HashSet是一個無序的集合,基於HashMap實現;TreeSet是一個有序的集合,基於TreeMap實現。
- HashSet集合中容許有null元素,TreeSet集合中不容許有null元素。
- HashSet和TreeSet都是非同步!在使用Iterator進行迭代的時候要注意fail-fast。
4. LinkedHashSet
1. LinkedHashSet 結構圖

LinkedHashSet類:LinkedHashSet正好介於HashSet和TreeSet之間,它也是一個hash表,但它同時維護了一個雙鏈表來記錄插入的順序,基本方法的複雜度爲O(1)。
當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。
2. LinkedHashSet 重要特色
- 繼承自HashSet,與HashSet惟一的區別是LinkedHashSet內部使用的是LinkHashMap((哈希表+鏈表+紅黑樹)+雙向鏈表)。
- LinkedHashSet在迭代訪問Set中的所有元素時,性能比HashSet好,可是插入時性能稍微遜色於HashSet。
- 非同步,線程不安全,存取速度快(同步封裝 Set s = Collections.synchronizedSet(new LinkedHashSet(...));)
- 其餘同HashSet
- 維護插入順序,LinkedHashSet使用LinkedHashMap對象來存儲它的元素,插入到LinkedHashSet中的元素其實是被看成LinkedHashMap的鍵保存起來的
- LinkedHashMap的每個鍵值對都是經過內部的靜態類Entry<K, V>實例化的。這個 Entry<K, V>類繼承了HashMap.Entry類。這個靜態類增長了兩個成員變量,before和after來維護LinkedHasMap元素的插入順序。這兩個成員變量分別指向前一個和後一個元素,這讓LinkedHashMap也有相似雙向鏈表的表現。
4. ConcurrentSkipListSet
1. ConcurrentSkipListSet 結構圖

2. ConcurrentSkipListSet 重要特色
- 一個基於
ConcurrentSkipListMap
的可縮放併發 NavigableSet
實現。set 的元素能夠根據它們的天然順序進行排序,也能夠根據建立 set 時所提供的 Comparator
進行排序,具體取決於使用的構造方法。
- 此實現爲 contains、add、remove 操做及其變體提供預期平均 log(n) 時間開銷。多個線程能夠安全地併發執行插入、移除和訪問操做。迭代器是弱一致 的,返回的元素將反映迭代器建立時或建立後某一時刻的 set 狀態。它們不 拋出
ConcurrentModificationException
,能夠併發處理其餘操做。升序排序視圖及其迭代器比降序排序視圖及其迭代器更快。
- 請注意,與在大多數 collection 中不一樣,這裏的 size 方法不是 一個固定時間 (constant-time) 操做。因爲這些 set 的異步特性,肯定元素的當前數目須要遍歷元素。此外,批量操做 addAll、removeAll、retainAll 和 containsAll 並不 保證能以原子方式 (atomically) 執行。例如,與 addAll 操做一塊兒併發操做的迭代器只能查看某些附加元素。
- 此類不容許使用 null 元素,由於沒法可靠地將 null 參數及返回值與不存在的元素區分開來。
4. CopyOnWriteArraySet (JUC)
1. CopyOnWriteArraySet 結構圖

2. CopyOnWriteArraySet 重要特色
-
CopyOnWriteArraySet 是線程安全的 Set,它是
由 CopyOnWriteArrayList 實現,
內部持有一個 CopyOnWriteArrayList 引用,全部的操做都是由 CopyOnWriteArrayList 來實現的,區別就是 CopyOnWriteArraySet 是無序的,而且不容許存放重複值。因爲是一個Set,因此也不支持隨機索引元素。
- 適合元素比較少,而且讀取操做高於更新(add/set/remove)操做的場景
- 因爲每次更新須要複製內部數組,因此更新操做(add、set 和 remove 等等)開銷比較大。
- 內部的迭代器 iterator 使用了不變的「快照」技術,存儲了內部數組快照, 因此它的 iterator 不支持可變remove、set、add操做,可是經過迭代器進行併發讀取時效率很高。
- 它是線程安全的。