Java-HashSet/LinkedHashSet整理




Table of Contents

  1. HashSet
    1. 概述
    2. 構造方法
    3. add方法
    4. contains方法
    5. remove方法
    6. clone方法
    7. 注意
  2. LinkedHashSet
    1. 概述
    2. 構造方法
    3. 總結

HashSet

概述

  • HashSet 是基於 HashMap 實現的,HashSet 底層採用 HashMap 來保存所有元素

  • 所有放入 HashSet 中的集合元素實際上由 HashMap 的 key 來保存,而 HashMap 的 value 則存儲了一個 PRESENT,它是一個靜態的 Object 對象。

  • HashSet 的絕大部分方法都是通過調用 HashMap 的方法來實現的,因此 HashSet 和 HashMap 兩個集合在實現本質上是相同的。 我們應該爲保存到 HashSet 中的對象覆蓋 hashCode() 和 equals() 方法

我們先通過 HashSet 最簡單的構造函數和幾個成員變量來看一下,證明其底層是 HashMap:

1
2
3
4
5
6
7
8
9
10
11
12
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}

默認情況下采用的是 initial capacity爲16,load factor 爲 0.75。

構造方法

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
/**
* 默認的無參構造器,構造一個空的HashSet。
*
* 實際底層會初始化一個空的HashMap,並使用默認初始容量爲16和加載因子0.75。
*/
public HashSet() {
map = new HashMap<E,Object>();
}
/**
* 構造一個包含指定collection中的元素的新set。
*
* 實際底層使用默認的加載因子0.75和足以包含指定collection中所有元素的初始容量來創建一個HashMap。
* @param c 其中的元素將存放在此set中的collection。
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
/**
* 以指定的initialCapacity和loadFactor構造一個空的HashSet。
*
* 實際底層以相應的參數構造一個空的HashMap。
* @param initialCapacity 初始容量。
* @param loadFactor 加載因子。
*/
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<E,Object>(initialCapacity, loadFactor);
}
/**
* 以指定的initialCapacity構造一個空的HashSet。
*
* 實際底層以相應的參數及加載因子loadFactor爲0.75構造一個空的HashMap。
* @param initialCapacity 初始容量。
*/
public HashSet(int initialCapacity) {
map = new HashMap<E,Object>(initialCapacity);
}
/**
* 以指定的initialCapacity和loadFactor構造一個新的空鏈接哈希集合。此構造函數爲包訪問權限,不對外公開,
* 實際只是是對LinkedHashSet的支持。
*
* 實際底層會以指定的參數構造一個空LinkedHashMap實例來實現。
* @param initialCapacity 初始容量。
* @param loadFactor 加載因子。
* @param dummy 標記。
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}

add方法

1
2
3
4
5
6
7
8
/**
* @param e 將添加到此set中的元素。
* @return 如果此set尚未包含指定元素,則返回true。
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

由於 HashMap 的 put() 方法添加 key-value 對時,當新放入 HashMap 的 Entry 中 key 與集合中原有 Entry 的 key 相同(hashCode()返回值相等,通過 equals 比較也返回 true),新添加的 Entry 的 value 會將覆蓋原來 Entry 的 value(HashSet 中的 value 都是PRESENT),但key不會有任何改變。

該方法如果添加的是在 HashSet 中不存在的,則返回 true;如果添加的元素已經存在,返回 false。其原因在於我們之前提到的關於 HashMap 的 put 方法。該方法在添加 key 不重複的鍵值對的時候,會返回 null。

contains方法

1
2
3
4
5
6
7
8
9
10
11
/**
* 如果此set包含指定元素,則返回true。
* 更確切地講,當且僅當此set包含一個滿足(o==null ? e==null : o.equals(e))的e元素時,返回true。
*
* 底層實際調用HashMap的containsKey判斷是否包含指定key。
* @param o 在此set中的存在已得到測試的元素。
* @return 如果此set包含指定元素,則返回true。
*/
public boolean contains(Object o) {
return map.containsKey(o);
}

remove方法

/**

 * 如果指定元素存在於此set中,則將其移除。更確切地講,如果此set包含一個滿足(o==null ? e==null : o.equals(e))的元素e,
 * 則將其移除。如果此set已包含該元素,則返回true
 *
 * 底層實際調用HashMap的remove方法刪除指定Entry。
 * @param o 如果存在於此set中則需要將其移除的對象。
 * @return 如果set包含指定元素,則返回true。
 */
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}

clone方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 返回此HashSet實例的淺表副本:並沒有複製這些元素本身。
*
* 底層實際調用HashMap的clone()方法,獲取HashMap的淺表副本,並設置到HashSet中。
*/
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();
}
}

注意

  • 對於 HashSet 中保存的對象,請注意正確重寫其 equals 和 hashCode 方法,以保證放入的對象的唯一性。這兩個方法是比較重要的,希望大家在以後的開發過程中需要注意一下

LinkedHashSet

概述

LinkedHashSet 首先我們需要知道的是它是一個 Set 的實現,所以它其中存的肯定不是鍵值對,而是值。此實現與 HashSet 的不同之處在於,LinkedHashSet 維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可爲插入順序或是訪問順序。

LinkedHashSet 底層使用 LinkedHashMap 來保存所有元素,它繼承與 HashSet,其所有的方法操作上又與 HashSet 相同,因此 LinkedHashSet 的實現上非常簡單,只提供了四個構造方法,並通過傳遞一個標識參數,調用父類的構造器,底層構造一個 LinkedHashMap 來實現,在相關操作上與父類 HashSet 的操作相同,直接調用父類 HashSet 的方法即可。LinkedHashSet 的源代碼如下:

構造方法

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 以指定的initialCapacity和loadFactor構造一個新的空鏈接哈希集合。
* 此構造函數爲包訪問權限,不對外公開,實際只是是對LinkedHashSet的支持。
*
* 實際底層會以指定的參數構造一個空LinkedHashMap實例來實現。
* @param initialCapacity 初始容量。
* @param loadFactor 加載因子。
* @param dummy 標記。
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}

LinkedHashSet 通過繼承 HashSet,底層使用 LinkedHashMap,以很簡單明瞭的方式來實現了其自身的所有功能。

其他代碼:

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
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
private static final long serialVersionUID = -2851667679971038690L;
/**
* 構造一個帶有指定初始容量和加載因子的新空鏈接哈希set。
*
* 底層會調用父類的構造方法,構造一個有指定初始容量和加載因子的LinkedHashMap實例。
* @param initialCapacity 初始容量。
* @param loadFactor 加載因子。
*/
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
/**
* 構造一個帶指定初始容量和默認加載因子0.75的新空鏈接哈希set。
*
* 底層會調用父類的構造方法,構造一個帶指定初始容量和默認加載因子0.75的LinkedHashMap實例。
* @param initialCapacity 初始容量。
*/
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
/**
* 構造一個帶默認初始容量16和加載因子0.75的新空鏈接哈希set。
*
* 底層會調用父類的構造方法,構造一個帶默認初始容量16和加載因子0.75的LinkedHashMap實例。
*/
public LinkedHashSet() {
super(16, .75f, true);
}
/**
* 構造一個與指定collection中的元素相同的新鏈接哈希set。
*
* 底層會調用父類的構造方法,構造一個足以包含指定collection
* 中所有元素的初始容量和加載因子爲0.75的LinkedHashMap實例。
* @param c 其中的元素將存放在此set中的collection。
*/
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
}

總結

  • LinkedHashSet 是 Set 的一個具體實現,其維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可爲插入順序或是訪問順序。
  • LinkedHashSet 繼承與 HashSet,並且其內部是通過 LinkedHashMap 來實現的。有點類似於我們之前說的LinkedHashMap 其內部是基於 Hashmap 實現一樣,不過還是有一點點區別的(具體的區別大家可以自己去思考一下)
  • 如果我們需要迭代的順序爲插入順序或者訪問順序,那麼 LinkedHashSet 是需要你首先考慮的

參考:


Table of Contents

  1. HashSet
    1. 概述
    2. 構造方法
    3. add方法
    4. contains方法
    5. remove方法
    6. clone方法
    7. 注意
  2. LinkedHashSet
    1. 概述
    2. 構造方法
    3. 總結

HashSet

概述

  • HashSet 是基於 HashMap 實現的,HashSet 底層採用 HashMap 來保存所有元素

  • 所有放入 HashSet 中的集合元素實際上由 HashMap 的 key 來保存,而 HashMap 的 value 則存儲了一個 PRESENT,它是一個靜態的 Object 對象。

  • HashSet 的絕大部分方法都是通過調用 HashMap 的方法來實現的,因此 HashSet 和 HashMap 兩個集合在實現本質上是相同的。 我們應該爲保存到 HashSet 中的對象覆蓋 hashCode() 和 equals() 方法

我們先通過 HashSet 最簡單的構造函數和幾個成員變量來看一下,證明其底層是 HashMap:

1
2
3
4
5
6
7
8
9
10
11
12
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}

默認情況下采用的是 initial capacity爲16,load factor 爲 0.75。

構造方法

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
/**
* 默認的無參構造器,構造一個空的HashSet。
*
* 實際底層會初始化一個空的HashMap,並使用默認初始容量爲16和加載因子0.75。
*/
public HashSet() {
map = new HashMap<E,Object>();
}
/**
* 構造一個包含指定collection中的元素的新set。
*
* 實際底層使用默認的加載因子0.75和足以包含指定collection中所有元素的初始容量來創建一個HashMap。
* @param c 其中的元素將存放在此set中的collection。
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
/**
* 以指定的initialCapacity和loadFactor構造一個空的HashSet。
*
* 實際底層以相應的參數構造一個空的HashMap。
* @param initialCapacity 初始容量。
* @param loadFactor 加載因子。
*/
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<E,Object>(initialCapacity, loadFactor);
}
/**
* 以指定的initialCapacity構造一個空的HashSet。
*
* 實際底層以相應的參數及加載因子loadFactor爲0.75構造一個空的HashMap。
* @param initialCapacity 初始容量。
*/
public HashSet(int initialCapacity) {
map = new HashMap<E,Object>(initialCapacity);
}
/**
* 以指定的initialCapacity和loadFactor構造一個新的空鏈接哈希集合。此構造函數爲包訪問權限,不對外公開,
* 實際只是是對LinkedHashSet的支持。
*
* 實際底層會以指定的參數構造一個空LinkedHashMap實例來實現。
* @param initialCapacity 初始容量。
* @param loadFactor 加載因子。
* @param dummy 標記。
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}

add方法

1
2
3
4
5
6
7
8
/**
* @param e 將添加到此set中的元素。
* @return 如果此set尚未包含指定元素,則返回true。
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

由於 HashMap 的 put() 方法添加 key-value 對時,當新放入 HashMap 的 Entry 中 key 與集合中原有 Entry 的 key 相同(hashCode()返回值相等,通過 equals 比較也返回 true),新添加的 Entry 的 value 會將覆蓋原來 Entry 的 value(HashSet 中的 value 都是PRESENT),但key不會有任何改變。

該方法如果添加的是在 HashSet 中不存在的,則返回 true;如果添加的元素已經存在,返回 false。其原因在於我們之前提到的關於 HashMap 的 put 方法。該方法在添加 key 不重複的鍵值對的時候,會返回 null。

contains方法

1
2
3
4
5
6
7
8
9
10
11
/**
* 如果此set包含指定元素,則返回true。
* 更確切地講,當且僅當此set包含一個滿足(o==null ? e==null : o.equals(e))的e元素時,返回true。
*
* 底層實際調用HashMap的containsKey判斷是否包含指定key。
* @param o 在此set中的存在已得到測試的元素。
* @return 如果此set包含指定元素,則返回true。
*/
public boolean contains(Object o) {
return map.containsKey(o);
}

remove方法

/**

 * 如果指定元素存在於此set中,則將其移除。更確切地講,如果此set包含一個滿足(o==null ? e==null : o.equals(e))的元素e,
 * 則將其移除。如果此set已包含該元素,則返回true
 *
 * 底層實際調用HashMap的remove方法刪除指定Entry。
 * @param o 如果存在於此set中則需要將其移除的對象。
 * @return 如果set包含指定元素,則返回true。
 */
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}

clone方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 返回此HashSet實例的淺表副本:並沒有複製這些元素本身。
*
* 底層實際調用HashMap的clone()方法,獲取HashMap的淺表副本,並設置到HashSet中。
*/
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();
}
}

注意

  • 對於 HashSet 中保存的對象,請注意正確重寫其 equals 和 hashCode 方法,以保證放入的對象的唯一性。這兩個方法是比較重要的,希望大家在以後的開發過程中需要注意一下

LinkedHashSet

概述

LinkedHashSet 首先我們需要知道的是它是一個 Set 的實現,所以它其中存的肯定不是鍵值對,而是值。此實現與 HashSet 的不同之處在於,LinkedHashSet 維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可爲插入順序或是訪問順序。

LinkedHashSet 底層使用 LinkedHashMap 來保存所有元素,它繼承與 HashSet,其所有的方法操作上又與 HashSet 相同,因此 LinkedHashSet 的實現上非常簡單,只提供了四個構造方法,並通過傳遞一個標識參數,調用父類的構造器,底層構造一個 LinkedHashMap 來實現,在相關操作上與父類 HashSet 的操作相同,直接調用父類 HashSet 的方法即可。LinkedHashSet 的源代碼如下:

構造方法

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 以指定的initialCapacity和loadFactor構造一個新的空鏈接哈希集合。
* 此構造函數爲包訪問權限,不對外公開,實際只是是對LinkedHashSet的支持。
*
* 實際底層會以指定的參數構造一個空LinkedHashMap實例來實現。
* @param initialCapacity 初始容量。
* @param loadFactor 加載因子。
* @param dummy 標記。
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}

LinkedHashSet 通過繼承 HashSet,底層使用 LinkedHashMap,以很簡單明瞭的方式來實現了其自身的所有功能。

其他代碼:

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
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
private static final long serialVersionUID = -2851667679971038690L;
/**
* 構造一個帶有指定初始容量和加載因子的新空鏈接哈希set。
*
* 底層會調用父類的構造方法,構造一個有指定初始容量和加載因子的LinkedHashMap實例。
* @param initialCapacity 初始容量。
* @param loadFactor 加載因子。
*/
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
/**
* 構造一個帶指定初始容量和默認加載因子0.75的新空鏈接哈希set。
*
* 底層會調用父類的構造方法,構造一個帶指定初始容量和默認加載因子0.75的LinkedHashMap實例。
* @param initialCapacity 初始容量。
*/
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
/**
* 構造一個帶默認初始容量16和加載因子0.75的新空鏈接哈希set。
*
* 底層會調用父類的構造方法,構造一個帶默認初始容量16和加載因子0.75的LinkedHashMap實例。
*/
public LinkedHashSet() {
super(16, .75f, true);
}
/**
* 構造一個與指定collection中的元素相同的新鏈接哈希set。
*
* 底層會調用父類的構造方法,構造一個足以包含指定collection
* 中所有元素的初始容量和加載因子爲0.75的LinkedHashMap實例。
* @param c 其中的元素將存放在此set中的collection。
*/
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
}

總結

  • LinkedHashSet 是 Set 的一個具體實現,其維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可爲插入順序或是訪問順序。
  • LinkedHashSet 繼承與 HashSet,並且其內部是通過 LinkedHashMap 來實現的。有點類似於我們之前說的LinkedHashMap 其內部是基於 Hashmap 實現一樣,不過還是有一點點區別的(具體的區別大家可以自己去思考一下)
  • 如果我們需要迭代的順序爲插入順序或者訪問順序,那麼 LinkedHashSet 是需要你首先考慮的

參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章