本文目錄
1 HashSet
1.1 HashSet的定義
(1)JDK中對HashSet的定義如下:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
HashSet繼承AbstractSet類,實現Set、Cloneable、Serializable接口。其中AbstractSet提供 Set 接口的骨幹實現,從而最大限度地減少了實現此接口所需的工作。Set接口是一種不包括重複元素的Collection,它維持它自己的內部排序,所以隨機訪問沒有任何意義。
(2)HashSet的基本屬性:
基於HashMap實現,底層使用HashMap保存所有元素:
private transient HashMap<E,Object> map;
//定義一個Object對象作爲HashMap的value
private static final Object PRESENT = new Object();
(3)構造函數:
/**
* 默認構造函數
* 初始化一個空的HashMap,並使用默認初始容量爲16和加載因子0.75。
*/
public HashSet() {
map = new HashMap<>();
}
/**
* 構造一個包含指定 collection 中的元素的新 set。
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
/**
* 構造一個新的空 set,其底層 HashMap 實例具有指定的初始容量和指定的加載因子
*/
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
/**
* 構造一個新的空 set,其底層 HashMap 實例具有指定的初始容量和默認的加載因子(0.75)。
*/
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/**
* 在API中我沒有看到這個構造函數,今天看源碼才發現(原來訪問權限爲包權限,不對外公開的)
* 以指定的initialCapacity和loadFactor構造一個新的空鏈接哈希集合。
* dummy 爲標識 該構造函數主要作用是對LinkedHashSet起到一個支持作用
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
從構造函數中可以看出HashSet所有的構造都是構造出一個新的HashMap,其中最後一個構造函數,爲包訪問權限是不對外公開,僅僅只在使用LinkedHashSet時纔會發生作用。
1.2 HashSet的方法
既然HashSet是基於HashMap,那麼對於HashSet而言,其方法的實現過程是相對比較簡單的。
(1)Iterator
public Iterator<E> iterator() {
return map.keySet().iterator();
}
iterator()方法返回對此 set 中元素進行迭代的迭代器。返回元素的順序並不是特定的。底層調用HashMap的keySet返回所有的key,這點反應了HashSet中的所有元素都是保存在HashMap的key中,value則是使用的PRESENT對象,該對象爲static final。
(2)size()
public int size() { return map.size(); }
size()返回此 set 中的元素的數量(set 的容量)。底層調用HashMap的size方法,返回HashMap容器的大小。
(3)isEmpty(),contains()
public boolean isEmpty() {
return map.isEmpty();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
//最終調用該方法進行節點查找
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//先檢查桶的頭結點是否存在
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//不是頭結點,則遍歷鏈表,如果是樹節點則使用樹節點的方法遍歷,直到找到,或者爲null
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
isEmpty(),判斷HashSet()集合是否爲空,爲空返回 true,否則返回false。contains(),判斷某個元素是否存在於HashSet()中,存在返回true,否則返回false。更加確切的講應該是要滿足這種關係才能返回true:(o==null ? e==null : o.equals(e))。底層調用containsKey判斷HashMap的key值是否爲空。
(4)add()、put()方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
map的put方法:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//確認初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//如果桶爲空,直接插入新元素,也就是entry
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果衝突,分爲三種情況
//key相等時讓舊entry等於新entry即可
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//紅黑樹情況
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果key不相等,則連成鏈表
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
這裏注意一點,hashset只是不允許重複的元素加入,而不是不允許元素連成鏈表,因爲只要key的equals方法判斷爲true時它們是相等的,此時會發生value的替換,因爲所有entry的value一樣,所以和沒有插入時一樣的。而當兩個hashcode相同但key不相等的entry插入時,仍然會連成一個鏈表,長度超過8時依然會和HashMap一樣擴展成紅黑樹。
當add方法發生衝突時,如果key相同,則替換value,如果key不同,則連成鏈表。
add()如果此 set 中尚未包含指定元素,則添加指定元素。如果此Set沒有包含滿足(e==null ? e2==null : e.equals(e2)) 的e2時,則將e2添加到Set中,否則不添加且返回false。
由於底層使用HashMap的put方法將key = e,value=PRESENT構建成key-value鍵值對,當此e存在於HashMap的key中,則value將會覆蓋原有value,但是key保持不變,所以如果將一個已經存在的e元素添加中HashSet中,新添加的元素是不會保存到HashMap中,所以這就滿足了HashSet中元素不會重複的特性。
(5)remove()
public boolean remove(Object o) { return map.remove(o)==PRESENT;}
remove如果指定元素存在於此 set 中,則將其移除。底層使用HashMap的remove方法刪除指定的Entry。
(6)clear()
public void clear() {
map.clear();
}
clear從此 set 中移除所有元素。底層調用HashMap的clear方法清除所有的Entry。
(7)clone()方法
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();
}
}
clone返回此 HashSet 實例的淺表副本:並沒有複製這些元素本身。
2 TreeSet
2.1 TreeSet的定義
源碼定義如下:
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
我們知道TreeMap是一個有序的二叉樹,那麼同理TreeSet同樣也是一個有序的,它的作用是提供有序的Set集合。通過源碼我們知道TreeSet基礎AbstractSet,實現NavigableSet、Cloneable、Serializable接口。
其中AbstractSet提供 Set 接口的骨幹實現,從而最大限度地減少了實現此接口所需的工作。
NavigableSet是擴展的 SortedSet,具有了爲給定搜索目標報告最接近匹配項的導航方法,這就意味着它支持一系列的導航方法。比如查找與指定目標最匹配項。Cloneable支持克隆,Serializable支持序列化。
同時在TreeSet中定義瞭如下幾個變量:
private transient NavigableMap<E,Object> m;
//PRESENT會被當做Map的value與key構建成鍵值對
private static final Object PRESENT = new Object();
其構造方法如下:
//默認構造方法,根據其元素的自然順序進行排序
public TreeSet() {
this(new TreeMap<E,Object>());
}
//構造一個包含指定 collection 元素的新 TreeSet,它按照其元素的自然順序進行排序。
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
//構造一個新的空 TreeSet,它根據指定比較器進行排序。
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
//構造一個與指定有序 set 具有相同映射關係和相同排序的新 TreeSet。
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
2.2 TreeSet的方法
(1)add:將指定的元素添加到此 set(如果該元素尚未存在於 set 中)。
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
//空樹時,判斷節點是否爲空
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
//非空樹,根據傳入比較器進行節點的插入位置查找
if (cpr != null) {
do {
parent = t;
//節點比根節點小,則找左子樹,否則找右子樹
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
//如果key的比較返回值相等,直接更新值(一般compareto相等時equals方法也相等)
else
return t.setValue(value);
} while (t != null);
}
else {
//如果沒有傳入比較器,則按照自然排序
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
//查找的節點爲空,直接插入,默認爲紅節點
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//插入後進行紅黑樹調整
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
(2)get:獲取元素
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
該方法與put的流程類似,只不過是把插入換成了查找。
(3)ceiling:返回此 set 中大於等於給定元素的最小元素;如果不存在這樣的元素,則返回 null。
public E ceiling(E e) {
(4)clear:移除此 set 中的所有元素。
public void clear() {
m.clear();
}
(5)clone:返回 TreeSet 實例的淺表副本。屬於淺拷貝。
public Object clone() {
TreeSet<E> clone = null;
try {
clone = (TreeSet<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
clone.m = new TreeMap<>(m);
return clone;
}
(6)comparator:返回對此 set 中的元素進行排序的比較器;如果此 set 使用其元素的自然順序,則返回 null。
public Comparator<? super E> comparator() {
return m.comparator();
}
(7)contains:如果此 set 包含指定的元素,則返回 true。
public boolean contains(Object o) {
return m.containsKey(o);
}
(8)descendingIterator:返回在此 set 元素上按降序進行迭代的迭代器。
public Iterator<E> descendingIterator() {
return m.descendingKeySet().iterator();
}
(9)descendingSet:返回此 set 中所包含元素的逆序視圖。
public NavigableSet<E> descendingSet() {
return new TreeSet<>(m.descendingMap());
}
(10)first:返回此 set 中當前第一個(最低)元素。
public E first() {
return m.firstKey();
}
(11)floor:返回此 set 中小於等於給定元素的最大元素;如果不存在這樣的元素,則返回 null。
public E floor(E e) {
return m.floorKey(e);
}
(12)headSet:返回此 set 的部分視圖,其元素嚴格小於 toElement。
public SortedSet<E> headSet(E toElement) {
return headSet(toElement, false);
}
(13)higher:返回此 set 中嚴格大於給定元素的最小元素;如果不存在這樣的元素,則返回 null。
public E higher(E e) {
return m.higherKey(e);
}
(14)isEmpty:如果此 set 不包含任何元素,則返回 true。
public boolean isEmpty() {
return m.isEmpty();
}
(15)iterator:返回在此 set 中的元素上按升序進行迭代的迭代器。
public Iterator<E> iterator() {
return m.navigableKeySet().iterator();
}
(16)last:返回此 set 中當前最後一個(最高)元素。
public E last() {
return m.lastKey();
}
(17)lower:返回此 set 中嚴格小於給定元素的最大元素;如果不存在這樣的元素,則返回 null。
public E lower(E e) {
return m.lowerKey(e);
}
(18)pollFirst:獲取並移除第一個(最低)元素;如果此 set 爲空,則返回 null。
public E pollFirst() {
Map.Entry<E,?> e = m.pollFirstEntry();
return (e == null) ? null : e.getKey();
}
(19)pollLast:獲取並移除最後一個(最高)元素;如果此 set 爲空,則返回 null。
public E pollLast() {
Map.Entry<E,?> e = m.pollLastEntry();
return (e == null) ? null : e.getKey();
}
(20)remove:將指定的元素從 set 中移除(如果該元素存在於此 set 中)
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
該方法與put類似,只不過把插入換成了刪除,並且要進行刪除後調整 。
(21)size:返回 set 中的元素數(set 的容量)。
public int size() {
return m.size();
}
(22)subSet:返回此 set 的部分視圖
/**
* 返回此 set 的部分視圖,其元素範圍從 fromElement 到 toElement。
*/
public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
E toElement, boolean toInclusive) {
return new TreeSet<>(m.subMap(fromElement, fromInclusive,
toElement, toInclusive));
}
/**
* 返回此 set 的部分視圖,其元素從 fromElement(包括)到 toElement(不包括)。
*/
public SortedSet<E> subSet(E fromElement, E toElement) {
return subSet(fromElement, true, toElement, false);
}
(23)tailSet:返回此 set 的部分視圖
/**
* 返回此 set 的部分視圖,其元素大於(或等於,如果 inclusive 爲 true)fromElement。
*/
public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
return new TreeSet<>(m.tailMap(fromElement, inclusive));
}
/**
* 返回此 set 的部分視圖,其元素大於等於 fromElement。
*/
public SortedSet<E> tailSet(E fromElement) {
return tailSet(fromElement, true);
}
3 LinkedHashSet
3.1 LinkedHashSet的定義
LinkedHashSet是HashSet的一個“擴展版本”,HashSet並不管什麼順序,不同的是LinkedHashSet會維護“插入順序”。HashSet內部使用HashMap對象來存儲它的元素,而LinkedHashSet內部使用LinkedHashMap對象來存儲和處理它的元素。這篇文章,我們將會看到LinkedHashSet內部是如何運作的及如何維護插入順序的。
我們首先着眼LinkedHashSet的構造函數。在LinkedHashSet類中一共有4個構造函數。這些構造函數都只是簡單地調用父類構造函數(如HashSet類的構造函數)。 下面看看LinkedHashSet的構造函數是如何定義的。
//Constructor --- 1
public LinkedHashSet(int initialCapacity, float loadFactor)
{
super(initialCapacity, loadFactor, true); //Calling super class constructor
}
//Constructor --- 2
public LinkedHashSet(int initialCapacity)
{
super(initialCapacity, .75f, true); //Calling super class constructor
}
//Constructor --- 3
public LinkedHashSet()
{
super(16, .75f, true); //Calling super class constructor
}
//Constructor --- 4
public LinkedHashSet(Collection<? extends E> c)
{
super(Math.max(2*c.size(), 11), .75f, true); //Calling super class constructor
addAll(c);
}
在上面的代碼片段中,你可能注意到4個構造函數調用的是同一個父類的構造函數。這個構造函數(父類的,譯者注)是一個包內私有構造函數(見下面的代碼,HashSet的構造函數沒有使用public公開,譯者注),它只能被LinkedHashSet使用。
這個構造函數需要初始容量,負載因子和一個boolean類型的啞值(沒有什麼用處的參數,作爲標記,譯者注)等參數。這個啞參數只是用來區別這個構造函數與HashSet的其他擁有初始容量和負載因子參數的構造函數,下面是這個構造函數的定義:
HashSet(int initialCapacity, float loadFactor, boolean dummy)
{
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
顯然,這個構造函數內部初始化了一個LinkedHashMap對象,這個對象恰好被LinkedHashSet用來存儲它的元素。
LinkedHashSet並沒有自己的方法,所有的方法都繼承自它的父類HashSet,因此,對LinkedHashSet的所有操作方式就好像對HashSet操作一樣。唯一的不同是內部使用不同的對象去存儲元素。在HashSet中,插入的元素是被當做HashMap的鍵來保存的,而在LinkedHashSet中被看作是LinkedHashMap的鍵。
這些鍵對應的值都是常量PRESENT(PRESENT是HashSet的靜態成員變量,譯者注)。
3.2 LinkedHashSet是如何維護插入順序的
LinkedHashSet使用LinkedHashMap對象來存儲它的元素,插入到LinkedHashSet中的元素實際上是被當作LinkedHashMap的鍵保存起來的。LinkedHashMap的每一個鍵值對都是通過內部的靜態類Entry<K, V>實例化的。這個 Entry<K, V>類繼承了HashMap.Entry類。這個靜態類增加了兩個成員變量,before和after來維護LinkedHasMap元素的插入順序。這兩個成員變量分別指向前一個和後一個元素,這讓LinkedHashMap也有類似雙向鏈表的表現。
private static class Entry<K,V> extends HashMap.Entry<K,V>
{
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
}
從上面代碼看到的LinkedHashMap內部類的前面兩個成員變量——before和after負責維護LinkedHashSet的插入順序。LinkedHashMap定義的成員變量header保存的是 這個雙向鏈表的頭節點。
接下來看一個例子就知道LinkedHashSet內部是如何工作的了:
public class LinkedHashSetExample
{
public static void main(String[] args)
{
//Creating LinkedHashSet
LinkedHashSet<String> set = new LinkedHashSet<String>();
//Adding elements to LinkedHashSet
set.add("BLUE");
set.add("RED");
set.add("GREEN");
set.add("BLACK");
}
}
如果你知道LinkedHashMap內部是如何工作的,就非常容易明白LinkedHashSet內部是如何工作的了。 https://blog.csdn.net/qq_41969790/article/details/106154225
附言:
本文整理來源於網絡、博客等資源,僅做個人學習筆記複習所用。
如果對你學習有用,請點贊共同學習!
如有侵權,請聯繫我修改或刪除!