前面的博客中我们分析了HashMap的源码,这一篇我们说一下HashSet和LinkedHashMap的实现逻辑。他们其实都是基于HashMap来实现,因此把它们放在一起讲还是比较合适的。
HashSet
HashSet也被称为集合,但该容器中只能存储不重复的对象。
//实现了Set接口,继承了AbstractSet类
//Set接口中定了一个Set具有那些行为和default方法
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;
//key所映射对象的虚拟值
private static final Object PRESENT = new Object();
.....
}
可以看到HashSet内部声明了一个HashMap对象,主要是利用了HashMap中的键值不能重复的特性,和HashSet中对象不能重复的特性一致,我们只需要将key所对应的value设为PRESENT即可,这样就可以完美的使用HashMap来实现HashSet了。
HashSet构造方法
public HashSet() {
map = new HashMap<>();
}
//初始值
public HashSet(Collection<? extends E> c) {
//最小容量16
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
//本质还是调用add方法
if (add(e))
modified = true;
return modified;
}
//指定初始容量和负载因子
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
//指定初始容量
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
可以看到HashSet构造方法基本都是对HashMap的构造方法的一层包装而已。其他增、删、遍历等方法都是直接调用HashMap对应的方法,就不再说了。
LinkedHashMap
HashMap是不记录插入顺序的,但是有时候我们在使用HashMap的时候,某些场景下,我们需要记录插入顺序,来完成一些特定的操作,而且也要保证按key查找的效率。因此LinkedHashMap就出现了,它在原有HashMap的基础下,再维护了一条链表,来记录插入顺序。
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
//链表第一个节点
transient LinkedHashMap.Entry<K,V> head;
//链表最后一个节点
transient LinkedHashMap.Entry<K,V> tail;
//此链表排序方法:true 访问顺序,false 插入顺序
//LinkedHashMap中默认都是false的,即按插入顺序
final boolean accessOrder;
...
}
LinkedHashMap是继承HashMap的,里面多维护了一条按插入顺序链表。
//LinkedHashMap存储对象的节点
static class Entry<K,V> extends HashMap.Node<K,V> {
//按指定顺序记录上一个和下个节点,迭代链表
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
LinkedHashMap 构造方法
//指定负载因子和容量
public LinkedHashMap(int initialCapacity, float loadFactor) {
//调用父类的构造方法
super(initialCapacity, loadFactor);
//指定链表迭代顺序:按插入
accessOrder = false;
}
//其它构造方法都差不多
....
LinkedHashMap 增加
可以看到LinkedHashMap没有实现自身的add方法,还是直接调用父类HashMap的add方法
//插入节点
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//该方法具体细节,可以查看前面介绍HashMap的博客
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
//创建一个链表节点,LinkedHashMap重写该方法
...newNode(hash, key, value, null);
//情况一:map中存在key对应的节点,更该节点的value值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
//情况二:map中不存在key对应的节点
afterNodeInsertion(evict);
return null;
}
//LinkedHashMap重写newNode方法,增加调用了linkNodeLast方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
//实例一个链表节点
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
//将当前节点添加到迭代链表尾部
linkNodeLast(p);
return p;
}
//LinkedHashMap重写newTreeNode方法,增加调用了linkNodeLast方法
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
//实例一个树节点
TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
//将当前节点添加到迭代链表尾部
linkNodeLast(p);
return p;
}
//将节点p添加值迭代链表尾部
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
//插入节点后
void afterNodeInsertion(boolean evict) {
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
//是否删除最老的一个节点
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
//将当前节点移动值迭代链表后尾部
void afterNodeAccess(Node<K,V> e) {
LinkedHashMap.Entry<K,V> last;
//获取尾部节点tail 赋值给 last
if (accessOrder && (last = tail) != e) {
//accessOrder默认为false,按插入顺序,因此不会移动当前节点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
可以看到LinkedHashMap中,afterNodeInsertion和afterNodeAccess都没有起作用,因此这两个方法应该是子类去使用,需要改变accessOrder的初始值和重写removeEldestEntry方法。
LinkedHashMap 删除
LinkedHashMap也没有重写remove方法,只是重写afterNodeRemoval方法,在节点删除后,将当前节点从迭代列表删除
//删除节点
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
//删除节点
inal Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {
...
++modCount;
--size;
//当前节点被删除后
afterNodeRemoval(node);
return node;
}
//当前节点被删除后
void afterNodeRemoval(Node<K,V> e) { // unlink
//将当前节点从迭代列表删除
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
LinkedHashMap 顺序遍历迭代器
由于LinkedHashMap维护了一条迭代列表,因此LinkedHashMap在遍历时直接遍历该迭代链表就可以了,因此新写了一个迭代器。
abstract class LinkedHashIterator {
//下一个节点
LinkedHashMap.Entry<K,V> next;
//当前节点
LinkedHashMap.Entry<K,V> current;
//保证‘结构变更’时,快速失败机制
int expectedModCount;
LinkedHashIterator() {
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext() {
return next != null;
}
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
current = e;
//指向下一个节点
next = e.after;
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
//调用removeNode删除当前节点
removeNode(hash(key), key, null, false, false);
//更新expectedModCount的值
expectedModCount = modCount;
}
}
关于顺序遍历器Iterator我在前面的博客中详细介绍过,这里就不在赘述了。
另外说明:LinkedHashMap中的顺序遍历都是基于对迭代链表的遍历来实现的,如forEach、replaceAll等方法中的遍历都是这样做的。
LinkedHashMap 并行遍历迭代器
LinkedHashMap没有重新定义个并行遍历器,直接使用父类的方法来构造并行遍历迭代器