1 HashSet源码解析
HashSet
也是一种java容器,这里不再赘述hash
的概念原理等一大堆东西了,需要在啰嗦一句的是hash表
是基于快速存取的角度设计的,也是一种典型的空间换时间的做法
先来看下Set
的特点:Set
元素无顺序,且元素不可以重复
无顺序,由于散列的缘故;不可重复,HashMap
的key
就是不能重复的。HashSet
就是基于HashMap
的key
来实现的,整个HashSet
中基本所有方法都是调用的HashMap
的方法。利用HashMap
可以实现两点:1.不可重复,2.快速查找。
1.1 定义
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
HashSet
继承了AbstractSet
抽象类,并实现了Set、Cloneable、Serializable
接口。AbstractSet
是一个抽象类,对一些基础的set
操作进行封装。继续来看下Set
接口的定义:
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();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT);
}
}
Set
接口和java.util.List
接口一样也实现了Collection
接口,但是Set
和List
所不同的是,Set
没有get
等跟下标相关的一些操作方法,那怎么取值呢?使用迭代器Iterator
1.2 底层存储
// 底层使用HashMap来保存HashSet的元素
private transient HashMap<E,Object> 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
呢?
比如写成这样子private final Object PRESENT = null;
我们都知道的是,Java
首先将变量PRESENT
分配在栈空间,而将new
出来的Object
分配到堆空间,这里的new Object()
是占用堆内存的(一个空的Object
对象占用8byte
),而null
值我们知道,是不会在堆空间分配内存的。
那么想一想这里为什么不使用null
值。看一个异常类java.lang.NullPointerException
,这绝对是Java
程序员的一个噩梦,这是所有Java
程序猿都会遇到的一个异常,你看到这个异常你以为很好解决,但是有些时候也不是那么容易解决,Java
号称没有指针,但是处处碰到NullPointerException
。所以啊,为了从根源上避免NullPointerException
的出现,浪费8byte
又怎么样
1.3 构造方法
/** * 使用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
是无意义的,只是为了区分其他构造方法。
1.4 增加和删除
/**
* 利用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;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
/**
* 删除指定集合c中的所有元素,该方法在AbstractSet中
*/
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(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;
}
1.5 是否包含
/** * 利用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
方法,效率是非常之快的。
1.6 容量检查
/** * 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();
}
以上代码都很简单,因为基本都是基于HashMap
实现
2 迭代器
2.1 HashMap的迭代器
我们看到Collection
实现了Iterable
接口,并且要求其子类实现一个返回Iterator
接口的iterator()
方法。那么既然HashSet
是Collection
的孙子类,那么HashSet
也应该实现了一个返回Iterator
接口的iterator()
方法,对不对,我们去看看
/** * 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();
}
HashSet
的iterator()
方法竟然也是利用HashMap
实现的
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
HashMap
的keySet()
方法的返回值竟然是一个Set
,具体实现是KeySet
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()
方法返回的是一个new KeyIterator()
方法
Iterator<K> newKeyIterator() {
return new KeyIterator();
}
new KeyIterator()
方法返回的又是一个KeyIterator()
方法
private final class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
}
}
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指向下一个节点,并判断是否为null32 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 ;
}
}
HashIterator
这个类(也是HashMap
的内部类),其中nextEntry()
这个方法,该方法主要思路是,首选拿去HashMap
低层数组中第一个不为null
的节点,每次调用迭代器的next()
方法,就用该节点next
一下,当当前节点next
到最后为null
,就拿数组中下一个不为null
的节点继续遍历。什么意思呢,就是循环从数组第一个索引开始,遍历整个Hash
表
2.2 LinkedHashMap的迭代器
看完HashMap
的Iterator
实现,再来看下LinkedHashMap
是怎么实现的吧(直接看最核心代码)
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;
}
}
可以看出LinkedHashMap
的迭代器,不在遍历真个Hash
表,而只是遍历其自身维护的双向循环链表,这样就不在需要对数组中是否为空节点进行的判断。所以说LinkedHashMap
在迭代器上的效率面通常是高与HashMap
的,既然这里是通常,那么什么时候不通常呢,那就是HashMap
中元素较少,分布均匀,没有空节点的时候。
网友评论