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() 方法进行去重,性能比较通常。对象

相关文章
相关标签/搜索