Java 容器源碼分析之 Set

Set 表示由無重複對象組成的集合,也是集合框架中重要的一種集合類型,直接擴展自 Collection 接口。在一個 Set 中,不能有兩個引用指向同一個對象,或兩個指向 null 的引用。若是對象 a 和 b 的引用知足條件 a.equals(b),那麼這兩個對象也不能同時出如今集合中。java

一般 Set 是不要求元素有序的,但也有一些有序的實現,如 SortedMap 接口、LinkedHashSet 接口等。安全

概述

Set 的具體實現一般都是基於 Map 的。由於 Map 中鍵是惟一的,於是在基於 Map 實現 Set 時,只須要關心 Map 中的鍵,和鍵關聯的值不須要有意義,使用一個任意的對象「佔位」便可。咱們在前面分析 Map 中的迭代器時,KeySet() 方法獲得的就是一個 Set。多線程

前面咱們分析過 Map 接口的幾個具體實現,通用的實現 HahsMap ,插入或訪問序的 LinkedHashMap , 按照鍵升序的 TreeMap。一樣,在 Set 的具體實現中,也有 HashSet 、 LinkedHashSet 和 TreeSet 等,分別和 Map 一一對應,它們的特性對應着相應的 Map 實現的特性。下面基於 HashSet 的實現作一個簡略的介紹。框架

HashSet 的實現

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

public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;

private transient HashMap<E,Object> map;

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

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);
}
}

從成員變量和構造方法能夠清楚地看到,內部使用了一個 HahsMap,同時定義了一個無心義的空的靜態 Object 對象(佔用8byte) PRESENT。既然 map 中和鍵關聯的值沒有意義,爲何不乾脆使用 null 呢?咱們看一下 add() 方法:性能

1
2
3
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

Map 的 put() 方法在添加一個新的鍵時會返回 null,在更新一個已經存在的鍵關聯的值時會返回舊值。於是 Set 中的 add() 方法能夠據此判斷新加入的元素是否改變了集合,若是改變了就返回 true。於是 PRESENT 不可使用 null 。this

其它的方法這裏簡單地列一下,都是基於 map 實現的:spa

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
public boolean contains(Object o) {
return map.containsKey(o);
}

public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}

public Iterator<E> iterator() {
return map.keySet().iterator();
}

public int size() {
return map.size();
}

public boolean isEmpty() {
return map.isEmpty();
}

public void clear() {
map.clear();
}

@SuppressWarnings("unchecked")
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);
}
}

//序列化
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();

// Write out HashMap capacity and load factor
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor());

// Write out size
s.writeInt(map.size());

// Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
}

private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();

// Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}

// Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}

// Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
}

// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);

// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));

// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

小結

Set 的內部一般是基於 Map 來實現的,Map 中的 Key 構成了 Set,而 Value 所有使用一個無心義的 Object 。 Set 的特徵與其內部的 Set 的特徵是一致的。基於 HashMap 的 HashSet 是無序時的最佳通用實現,基於 LinkedHashMap 的 LinkedHashSet 保留插入或訪問的順序,基於 TreeMap 的 TreeSet 能夠按照元素升序排列,要求元素實現 Comaprable 接口或自定義比較器。線程

HashSet , LinkedHashSet, TreeSet 都不是線程安全的,在多線程環境下使用時要注意同步問題。code

CopyOnWriteArraySet 是一個線程安全的實現,可是並非基於 Map 實現的,而是經過 CopyOnWriteArrayList 實現的。使用 addIfAbsent() 方法進行去重,性能比較通常。對象

相關文章
相關標籤/搜索