今天的主角是HashSet,Set是什么东东,当然也是一种java容器了。
1
2
3
|
public
class
HashSet<E>
extends
AbstractSet<E>
implements
Set<E>, Cloneable, java.io.Serializable
|
我们看到HashSet继承了AbstractSet抽象类,并实现了Set、Cloneable、Serializable接口。AbstractSet是一个抽象类,对一些基础的set操作进行封装。继续来看下Set接口的定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
interface
Set<E>
extends
Collection<E> {
// Query Operations
int
size();
boolean
isEmpty();
boolean
contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
// Modification Operations
boolean
add(E e);
boolean
remove(Object o);
// Bulk Operations
boolean
containsAll(Collection<?> c);
boolean
addAll(Collection<?
extends
E> c);
boolean
retainAll(Collection<?> c);
boolean
removeAll(Collection<?> c);
void
clear();
// Comparison and hashing
boolean
equals(Object o);
int
hashCode();
}
|
发现了什么,Set接口和java.util.List接口一样也实现了Collection接口,但是Set和List所不同的是,Set没有get等跟下标先关的一些操作方法,那怎么取值呢?Iterator还记得吗,使用迭代器对不对。(不明白的回去看Iterator讲解)
2.底层存储
1
2
3
4
5
6
|
// 底层使用HashMap来保存HashSet的元素
private
transient
HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
// 由于Set只使用到了HashMap的key,所以此处定义一个静态的常量Object类,来充当HashMap的value
private
static
final
Object PRESENT =
new
Object();
|
看到这里就明白了,和我们前面说的一样,HashSet是用HashMap来保存数据,而主要使用到的就是HashMap的key。
看到private static final Object PRESENT = new Object();不知道你有没有一点疑问呢。这里使用一个静态的常量Object类来充当HashMap的value,既然这里map的value是没有意义的,为什么不直接使用null值来充当value呢?比如写成这样子privatefinal Object PRESENT = null;我们都知道的是,Java首先将变量PRESENT分配在栈空间,而将new出来的Object分配到堆空间,这里的new Object()是占用堆内存的(一个空的Object对象占用8byte),而null值我们知道,是不会在堆空间分配内存的。那么想一想这里为什么不使用null值。想到什么吗,看一个异常类java.lang.NullPointerException, 噢买尬,这绝对是Java程序员的一个噩梦,这是所有Java程序猿都会遇到的一个异常,你看到这个异常你以为很好解决,但是有些时候也不是那么容易解决,Java号称没有指针,但是处处碰到NullPointerException。所以啊,为了从根源上避免NullPointerException的出现,浪费8个byte又怎么样,在下面的代码中我再也不会写这样的代码啦if (xxx == null) { … } else {….},好爽。
3.构造方法
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
|
/**
* 使用HashMap的默认容量大小16和默认加载因子0.75初始化map,构造一个HashSet
*/
public
HashSet() {
map =
new
HashMap<E,Object>();
}
/**
* 构造一个指定Collection参数的HashSet,这里不仅仅是Set,只要实现Collection接口的容器都可以
*/
public
HashSet(Collection<?
extends
E> c) {
map =
new
HashMap<E,Object>(Math. max((
int
) (c.size()/.75f) +
1
,
16
));
// 使用Collection实现的Iterator迭代器,将集合c的元素一个个加入HashSet中
addAll(c);
}
/**
* 使用指定的初始容量大小和加载因子初始化map,构造一个HashSet
*/
public
HashSet(
int
initialCapacity,
float
loadFactor) {
map =
new
HashMap<E,Object>(initialCapacity, loadFactor);
}
/**
* 使用指定的初始容量大小和默认的加载因子0.75初始化map,构造一个HashSet
*/
public
HashSet(
int
initialCapacity) {
map =
new
HashMap<E,Object>(initialCapacity);
}
/**
* 不对外公开的一个构造方法(默认default修饰),底层构造的是LinkedHashMap,dummy只是一个标示参数,无具体意义
*/
HashSet(
int
initialCapacity,
float
loadFactor,
boolean
dummy) {
map =
new
LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}
|
从构造方法可以很轻松的看出,HashSet的底层是一个HashMap,理解了HashMap后,这里没什么可说的。只有最后一个构造方法有写区别,这里构造的是LinkedHashMap,该方法不对外公开,实际上是提供给LinkedHashSet使用的,而第三个参数dummy是无意义的,只是为了区分其他构造方法。
4.增加和删除
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
|
/**
* 利用HashMap的put方法实现add方法
*/
public
boolean
add(E e) {
return
map .put(e, PRESENT)==
null
;
}
/**
* 利用HashMap的remove方法实现remove方法
*/
public
boolean
remove(Object o) {
return
map .remove(o)==PRESENT;
}
/**
* 添加一个集合到HashSet中,该方法在AbstractCollection中
*/
public
boolean
addAll(Collection<?
extends
E> c) {
boolean
modified =
false
;
// 取得集合c迭代器Iterator
Iterator<?
extends
E> e = c.iterator();
// 遍历迭代器
while
(e.hasNext()) {
// 将集合c的每个元素加入到HashSet中
if
(add(e.next()))
modified =
true
;
}
return
modified;
}
/**
* 删除指定集合c中的所有元素,该方法在AbstractSet中
*/
public
boolean
removeAll(Collection<?> c) {
boolean
modified =
false
;
// 判断当前HashSet元素个数和指定集合c的元素个数,目的是减少遍历次数
if
(size() > c.size()) {
// 如果当前HashSet元素多,则遍历集合c,将集合c中的元素一个个删除
for
(Iterator<?> i = c.iterator(); i.hasNext(); )
modified |= remove(i.next());
}
else
{
// 如果集合c元素多,则遍历当前HashSet,将集合c中包含的元素一个个删除
for
(Iterator<?> i = iterator(); i.hasNext(); ) {
if
(c.contains(i.next())) {
i.remove();
modified =
true
;
}
}
}
return
modified;
}
|
5.是否包含
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/**
* 利用HashMap的containsKey方法实现contains方法
*/
public
boolean
contains(Object o) {
return
map .containsKey(o);
}
/**
* 检查是否包含指定集合中所有元素,该方法在AbstractCollection中
*/
public
boolean
containsAll(Collection<?> c) {
// 取得集合c的迭代器Iterator
Iterator<?> e = c.iterator();
// 遍历迭代器,只要集合c中有一个元素不属于当前HashSet,则返回false
while
(e.hasNext())
if
(!contains(e.next()))
return
false
;
return
true
;
}
|
由于HashMap基于hash表实现,hash表实现的容器最重要的一点就是可以快速存取,那么HashSet对于contains方法,利用HashMap的containsKey方法,效率是非常之快的。在我看来,这个方法也是HashSet最核心的卖点方法之一。
6.容量检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/**
* Returns the number of elements in this set (its cardinality).
*
* @return the number of elements in this set (its cardinality)
*/
public
int
size() {
return
map .size();
}
/**
* Returns <tt>true</tt> if this set contains no elements.
*
* @return <tt> true</tt> if this set contains no elements
*/
public
boolean
isEmpty() {
return
map .isEmpty();
}
|
1
2
3
4
5
6
7
8
9
10
|
/**
* Returns an iterator over the elements in this set. The elements
* are returned in no particular order.
*
* @return an Iterator over the elements in this set
* @see ConcurrentModificationException
*/
public
Iterator<E> iterator() {
return
map .keySet().iterator();
}
|
我cha,咋回事,HashSet的iterator()方法竟然也是利用HashMap实现的,我们去看看HashMap的keySet()方法是什么鬼。
1
2
3
4
|
public
Set<K> keySet() {
Set<K> ks = keySet;
return
(ks !=
null
? ks : (keySet =
new
KeySet()));
}
|
HashMap的keySet()方法的返回值竟然是一个Set,具体实现是一个叫KeySet的东东,KeySet又是什么鬼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
private
final
class
KeySet
extends
AbstractSet<K> {
public
Iterator<K> iterator() {
return
newKeyIterator();
}
public
int
size() {
return
size ;
}
public
boolean
contains(Object o) {
return
containsKey(o);
}
public
boolean
remove(Object o) {
return
HashMap.
this
.removeEntryForKey(o) !=
null
;
}
public
void
clear() {
HashMap.
this
.clear();
}
}
|
哦,KeySet是一个实现了AbstractSet的HashMap的内部类。而KeySet的iterator()方法返回的是一个newKeyIterator()方法,好绕好绕,头晕了。
1
2
3
|
Iterator<K> newKeyIterator() {
return
new
KeyIterator();
}
|
newKeyIterator()方法返回的又是一个KeyIterator()方法,what are you 弄啥嘞?
1
2
3
4
5
|
private
final
class
KeyIterator
extends
HashIterator<K> {
public
K next() {
return
nextEntry().getKey();
}
}
|
好吧,不想说什么了,继续往下看吧。
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
|
private
abstract
class
HashIterator<E>
implements
Iterator<E> {
// 下一个需要返回的节点
Entry<K,V> next;
// next entry to return
int
expectedModCount ;
// For fast-fail
int
index ;
// current slot
// 当前需要返回的节点
Entry<K,V> current;
// current entry
HashIterator() {
expectedModCount = modCount ;
if
(size >
0
) {
// advance to first entry
Entry[] t = table;
// 初始化next参数,将next赋值为HashMap底层的第一个不为null节点
while
(index < t.length && ( next = t[index ++]) ==
null
)
;
}
}
public
final
boolean
hasNext() {
return
next !=
null
;
}
final
Entry<K,V> nextEntry() {
if
(modCount != expectedModCount)
throw
new
ConcurrentModificationException();
// 取得HashMap底层数组中链表的一个节点
Entry<K,V> e = next;
if
(e ==
null
)
throw
new
NoSuchElementException();
// 将next指向下一个节点,并判断是否为null
if
((next = e.next) ==
null
) {
Entry[] t = table;
// 如果为null,则遍历真个数组,知道取得一个不为null的节点
while
(index < t.length && ( next = t[index ++]) ==
null
)
;
}
current = e;
// 返回当前节点
return
e;
}
public
void
remove() {
if
(current ==
null
)
throw
new
IllegalStateException();
if
(modCount != expectedModCount)
throw
new
ConcurrentModificationException();
Object k = current.key ;
current =
null
;
HashMap.
this
.removeEntryForKey(k);
expectedModCount = modCount ;
}
}
|
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
|
private
abstract
class
LinkedHashIterator<T>
implements
Iterator<T> {
// header.after为LinkedHashMap双向链表的第一个节点,因为LinkedHashMap的header节点不保存数据
Entry<K,V> nextEntry = header .after;
// 最后一次返回的节点
Entry<K,V> lastReturned =
null
;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int
expectedModCount = modCount;
public
boolean
hasNext() {
return
nextEntry != header;
}
public
void
remove() {
if
(lastReturned ==
null
)
throw
new
IllegalStateException();
if
(modCount != expectedModCount)
throw
new
ConcurrentModificationException();
LinkedHashMap.
this
.remove(lastReturned .key);
lastReturned =
null
;
expectedModCount = modCount ;
}
Entry<K,V> nextEntry() {
if
(modCount != expectedModCount)
throw
new
ConcurrentModificationException();
if
(nextEntry == header)
throw
new
NoSuchElementException();
// 将要返回的节点nextEntry赋值给lastReturned
// 将nextEntry赋值给临时变量e(因为接下来nextEntry要指向下一个节点)
Entry<K,V> e = lastReturned = nextEntry ;
// 将nextEntry指向下一个节点
nextEntry = e.after ;
// 放回当前需返回的节点
return
e;
}
}
|