Map源码解析之HashMap
Map源码解析之HashMap红黑树
Map源码解析之HashMap补充:集合、迭代器、compute、merge、replace
在分析完了HashMap源码之后,我们接着看另一种形式的map :LinkedHashMap。
LinkedHashMap是HashMap的子类,相比HashMap而言,其数据结构在HashMap的基础上多了双向链表将所有节点连接起来,从而保证LinkedHashMap的有序性。
一、主要的成员变量
双向链表的头结点:transient LinkedHashMap.Entry<K,V> head;
双向链表的尾节点:transient LinkedHashMap.Entry<K,V> tail;
双向链表的顺序,true表示访问顺序,false表示插入顺序,默认按照访问插入顺序:final boolean accessOrder;
二、主要的成员类
1. LinkedHashMap.Entry类
作为LinkedHashMap的节点类,继承了HashMap.Node类,多了 before、 after属性,分别指向该节点在双向链表中前一个及后一个节点。
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);
}
}
三、构造方法
1. public LinkedHashMap()
调用HashMap的构造器后,设置默认排序为插入顺序。
public LinkedHashMap() {
super();
accessOrder = false;
}
2. public LinkedHashMap(int initialCapacity)
相比public LinkedHashMap()
多设置了一个初始容量
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
3. public LinkedHashMap(int initialCapacity, float loadFactor)
相比public LinkedHashMap(int initialCapacity)
多设置了负载因子
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
4. public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
相比public LinkedHashMap(int initialCapacity, float loadFactor)
多设置了排序方式,不再使用默认排序。
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
5. public LinkedHashMap(Map<? extends K, ? extends V> m)
在public LinkedHashMap()
基础上加上参数m中的所有节点。
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
四、基础方法
1. LinkedHashMap#linkNodeLast方法
将一个节点加入到双向链表的最后面
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;
}
}
2. LinkedHashMap#newNode方法
重写了HashMap#newNode方法,新建一个节点并加入到双向链表最后面。
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;
}
3. LinkedHashMap#newTreeNode方法
重写了HashMap#newTreeNode方法,新建一个TreeNode并加入到双向链表最后面。
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;
}
4. LinkedHashMap#transterLinks方法
用节点dst替换src在双向链表中的位置。
private void transferLinks(LinkedHashMap.Entry<K,V> src,
LinkedHashMap.Entry<K,V> dst) {
LinkedHashMap.Entry<K,V> b = dst.before = src.before;
LinkedHashMap.Entry<K,V> a = dst.after = src.after;
if (b == null)
head = dst;
else
b.after = dst;
if (a == null)
tail = dst;
else
a.before = dst;
}
5. LinkedHashMap#replacementNode方法
重写了 HashMap#replacementNode方法,将TreeNode节点组装成LinkedHashMap.Entry,并取代原来节点在双向链表中的位置,主要用于去树化。
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
LinkedHashMap.Entry<K,V> t =
new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
transferLinks(q, t);
return t;
}
6. LinkedHashMap#replacementTreeNode方法
重写了HashMap#replaceTreementNode方法,将LinkedHashMap.Entry节点组装成TreeNode,并取代原来节点在双向链表中的位置,主要用于树化。
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);
transferLinks(q, t);
return t;
}
五、核心方法
在HashMap中我们已经提到了HashMap#afterNodeAccess, HashMap#afterNodeInsertion, HashMap#afterNodeRemoval方法,分别在节点被访问、被插入和被删除后会进行调用。
但是在HashMap中这3个方法都是空方法,没有逻辑,而LinkedHashMap正是通过重写这3个方法来维护双向链表。从而保证了可以复用HashMap中所有操作节点的方法而无需重写。
1. 访问
访问节点只有可能改变节点的访问顺序。
当LinkedHashMap采用访问顺序且该节点不是最后一个节点时,将该节点移到双向节点的最后面。
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
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;
}
}
2. 删除
删除节点需要将其从双向链表中删除。
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;
}
3. 插入
不同于访问的LinkedHashMap#afterNodeAccess和删除的LinkedHashMap#afterNodeRemoval主要用于维护双向链表中的对应节点,由于插入节点在LinkedHashMap#newNode和LinkedHashMap#newTreeNode的重写中已经将节点放入双向链表的最后面,LinkedHashMap#afterNodeInsertion不在需要进行维护双向链表的操作。
void afterNodeInsertion(boolean evict) { // possibly remove eldest
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;
}
由于LinkedHashMap#removeEldestEntry方法返回false,LinkedHashMap#afterNodeInsertion本身没有任何操作,其主要作用是用于子类通过重写removeEldestEntry的方式进行扩展。
根据removeEldestEntry的返回时,afterNodeInsertion有可能会删除双向链表的第一个节点。再加上LinkedHashMap#afterNodeAccess可以将访问过的节点移到链表最后面,这样子最近最少使用(LRU)的算法就可以实现了。
六、查询节点
1. LinkedHashMap#get方法
和HashMap#get方法相比,多了afterNodeAccess方法的执行。
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
2. LinkedHashMap#containsValue方法
HashMap#containsValue方法按照数组+链表进行遍历,而LinkedHashMap#containsValue直接遍历双向链表。
//HashMap#containsValue
public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) {
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}
//LinkedHashMap#containsValue
public boolean containsValue(Object value) {
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
V v = e.value;
if (v == value || (value != null && value.equals(v)))
return true;
}
return false;
}