在使用HashMap时,因为其插入的顺序是随机的,而我们又想按照插入的顺序进行遍历。那我们可以使用LinkedHashMap。
今天看看LinkedHashMap的源码,看是如何HashMao的插入有序。
一、构造
public class LinkedHashMap<K,V> extends HashMap<K,V>
首先它是继承与HashMap的,但要实现插入顺序,就必须记录每个节点的插入顺序,所以LinkedHashMap重写了MapEntry
static class Entry<K,V> extends HashMap.Node<K,V> { //before是节点的前置节点,after是节点的后继节点 LinkedHashMap.Entry<K,V> before, after; Entry(int hash, K key, V value, HashMap.Node<K,V> next) { super(hash, key, value, next); } }在HashMap的节点上加入了两个属性,一个是前置节点,一个后置节点,以记录节点的插入顺序。
二、查询顺序的实现
为了保证记录插入顺序,就将每个插入的节点用链表连起来,所以LinkedHashMap维护了一个表头和一个表尾。
transient LinkedHashMap.Entry<K,V> head; /** * The tail (youngest) of the doubly linked list. */ transient LinkedHashMap.Entry<K,V> tail;
三、插入节点
和hashMap一样,只不过LinkedHashMap重写了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; }
那么插入节点时,LinkedHashMap到底做了什么,我们可以进入到linkNodeLast这个方法中
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { //将当前节点插入到链表的末尾 LinkedHashMap.Entry<K,V> last = tail; tail = p; //如果last为空,说明集合中还没有节点,所以讲节点p设为头结点 if (last == null) head = p; //如果last不为空,则将p置于原先last节点的后面.修改节点p的前置节点指针,修改原本last节点的后置指针 else { p.before = last; last.after = p; } }
可以看到,每次插入都将新节点加到链表的末尾,并用节点的before和after指针指向前置节点和后置节点。以此记录节点顺序。
LinkedHashMap节点的删除
void afterNodeRemoval(Node<K,V> e) { // unlink //找出要删除节点的前置节点b和后继节点p LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; //释放节点p的所有引用 p.before = p.after = null; //如果要删除节点为头结点,则将头节点指针指向要删除节点p的后继,也就是p if (b == null) head = a; //如果要删除节点不为头结点,则将要删除节点p的前置节点的后继指针p.before指向要删除节点的后继节点 else b.after = a; //如果要删除节点为尾结点,则将尾节点指针指向要删除节点p的前置,也就是b if (a == null) tail = b; //如果要删除节点不为尾结点,则将要删除节点p的后继节点的前置指针a.before指向要删除节点的前置节点 else a.before = b; }
因为LinkedHashMap重写了afterNodeRemoval方法,可以看出,在删除节点时,将节点从记录插入顺序的链表中删除,还是原先对应的插入顺序。
总结:
LinkedHashMap使用两个指针记录节点的插入顺序
LinkedHashMap继承了hashMap,所以只需将改变hashMap结构的某些方法进行重写即可。