Java Collection 10 - HashSet

  关于 java.util.HashSet<E> 的部分笔记。HashSet集合是一个不会存储重复元素、遍历元素顺序无法保证的集合实现。本文演示代码段的执行环境基于JDK版本1.7

概述

  HashSet是一个以HashMap为底层数据结构的集合实现类,该实现类实现了Set接口中规定的集合处理方法API,而且允许存储null元素。HashSet集合具有集合内元素唯一,且无序的特点。由于底层数据结构依赖于散列表,所以HashSet的add(E e)、remove(Object o)、contains(Object o)、size()等方法可以实现常量时间的操作性能。

  HashSet是一个非线程安全的实现。如果需要在多线程环境使用HashSet进行操作,那么要么使用线程安全的实现类,要么对HashSet集合做一些额外的处理来保证HashSet集合可以满足多线程环境中的使用需求。常见的一种实现方式是:

1
Set s = Collections.synchronizedSet(new HashSet(...));

  同样,HashSet集合返回的迭代器也含有快速失败检查,如果在遍历当前HashSet集合的过程中对集合做了结构化修改,那么就会抛出ConcurrentModificationException。同样的,HashSet集合也无法保证可以捕捉到所有的并发修改操作。

继承关系

1
2
3
4
5
// HashSet<E>
--java.lang.Object
--java.util.AbstractCollection<E>
--java.util.AbstractSet<E>
--java.util.HashSet<E>

实现接口

类名 实现接口
HashSet<E> Iterable<E>, Collection<E>, Set<E>, Serializable, Cloneable

HashSet

Constructor Summary

public HashSet()

1
2
3
public HashSet() {
map = new HashMap<>();
}

  初始化一个空的set集合。底层依赖的hashmap的初始容量为16,加载因子是0.75。

public HashSet(Collection<? extends E> c)

1
2
3
4
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}

  初始化一个空set集合,并将集合c中的内容存储到set集合中。底层hashmap的初始容量由集合c中存储的元素数量决定。之后调用的addAll(Collection<? extends E> c)方法为AbstractCollection中实现的方法,内部调用了HashSet覆盖的父类方法add(E e)

public HashSet(int initialCapacity, float loadFactor)

1
2
3
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}

  初始化一个空的set集合。底层依赖的hashmap的初始容量为initialCapacity,加载因子值是loadFactor。

public HashSet(int initialCapacity)

1
2
3
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}

  初始化一个空的set集合。底层依赖的hashmap的初始容量为initialCapacity,加载因子值是默认的0.75。

HashSet(int initialCapacity, float loadFactor, boolean dummy)

1
2
3
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

  初始化一个空的set集合。底层依赖的hashmap的初始容量为initialCapacity,加载因子值是loadFactor。这个构造方法中多了一个参数dummy,这个参数除了区别构造方法之外没有其他用处,但是该构造方法返回了一个LinkedHashMap,在LinkedHashSet<E>中使用该构造方法来实现一个基于链表的Set集合。

部分方法

public Iterator<E> iterator()

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

  返回一个可以遍历当前HashSet集合的迭代器。该迭代器在遍历元素时不保证元素遍历的先后顺序。底层实际调用的是map中keySet集合的迭代器。

public int size()

1
2
3
public int size() {
return map.size();
}

  返回当前HashSet集合存储的元素数量。底层实际调用的是map的size()方法完成操作。

public boolean isEmpty()

1
2
3
public boolean isEmpty() {
return map.isEmpty();
}

  判断当前HashSet集合是否为空。如果为空返回true,否则返回false。底层实际调用的是map的isEmpty()方法完成操作。

public boolean contains(Object o)

1
2
3
public boolean contains(Object o) {
return map.containsKey(o);
}

  判断当前HashSet集合中是否含有元素o。如果存在返回true,否则返回false。底层实际调用的是map的containsKey(Object key)方法完成操作。

public boolean add(E e)

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

  将元素e加入到当前HashSet集合中。如果元素e是第一次被加入到集合中,那么由于map的put(E key, Object value)方法会返回该key在加入之前的value值,所以会返回null,所以第一次加入元素e时当前方法会返回true。如果非第一次加入,那么map的put(E key, Object value)会返回一个非null对象,所以当前方法会返回false。

public boolean remove(Object o)

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

  将元素o从当前HashSet集合中删除。由于在向map集合中存储元素时,key为当前HashSet集合存储的元素自身,value是一个初始化的Object对象,所以在调用map的remove(Object key)方法会返回该key在加入之前的value值,所以第一次删除元素时返回true。如果当前元素o被多次调用了remove(Object o)方法,那么第二次及之后就返回false。

public void clear()

1
2
3
public void clear() {
map.clear();
}

  清空HashSet集合的所有元素。底层实际调用的是map的clear()方法完成操作。

public Object clone()

1
2
3
4
5
6
7
8
9
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集合对象。该过程分两步进行,首先复制一个HashSet集合对象自身,接着复制HashSet集合底层的map结构,最后返回复制得到的对象。

private void writeObject(java.io.ObjectOutputStream s)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}

  将当前HashSet执行序列化操作并写入到流中。自定义方法,在执行序列化过程时会采用HashSet自己实现的过程来完成序列化操作。

private void readObject(java.io.ObjectInputStream s)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();

// Read in HashMap capacity and load factor and create backing HashMap
int capacity = s.readInt();
float loadFactor = s.readFloat();
map = (((HashSet)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));

// Read in size
int size = s.readInt();

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

  从流中取出序列化的数据内容并反序列化成内存中的一个HashSet对象。自定义方法,在执行反序列的过程时会用HashSet自己实现的过程来完成反序列过程。

涉及基础知识点

  1. NIL

参考文献

  1. NIL




------------- End of this article, thanks! -------------


  版权声明:本文由Nathan R. Lee创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处。
  本文作者为 Nathan R. Lee
  本文标题为 Java Collection 10 - HashSet
  本文链接为 https://marcuseddie.github.io/2018/java-Collection-HashSet.html