您的位置:首页 > 编程语言 > Java开发

Java容器类浅析三-----保证插入顺序的HashMap--LinkedHashMap的存取原理

2016-06-15 18:04 453 查看
上一节我们说到HashMap的存取原理,并一步步地分析了其主要的成员变量、构造函数以及体现其算法的put,get方法。本文将继续分析HashMap的子类—LindedHashMap,来解决上一节提到的,如何保证迭代顺序和插入顺序的一致性的问题。

一、概述

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>


LinkedHashMap直接继承于HashMap,且其内部属性和存取算法与HashMap基本一致。

那么为什么LinkedHashMap可以做到保证迭代顺序和插入顺序一致呢? 这就要归功于LinkedHashMap中的一个数据结构—Entry header了,header是一个双向链表中的首节点,该双向链表保存了entry插入的顺序,在迭代时,实际上是在迭代该双向链表。每次调用put()方法时,第一步和HashMap的put()方法一致,第二步就是将该元素的before节点指向header的before节点,after节点指向header,将header的before节点指向该元素,以完成双向链表的记录顺序。

二、成员变量

/**
* The head of the doubly linked list.
*/
private transient Entry<K,V> header;


双向链表的头结点。

/**
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
*
* @serial
*/
private final boolean accessOrder;


标识迭代顺序的标志,true表示按照访问顺序进行迭代(),false表示按照插入的顺序进行迭代。

三、构造函数

public LinkedHashMap() {
super();
accessOrder = false;
}

public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}

public LinkedHashMap(Map<? extends K, ? extends V> m) {
super(m);
accessOrder = false;
}

@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}


构造函数和HashMap的基本一致,只是多了将accessOrder设为false的操作;而且可以将一个HashMap的对象强转为LinkedHashMap的对象。

而且,LinkedHashMap重写了父类的钩子函数–init(),将双向链表的头结点初始化,并将其before、after节点指向了自己,其实就是对双向链表进行初始化。

四、put()方法

打开LinkedHashMap的源码,Ctrl+O,输入put,你会惊奇的发现这个类里面并没有put方法,说明LinkedHashMap没有重写父类的put()方法,只是继承了父类的put()方法。那么问题来了,如果没有重写put()方法,LinkedHashMap是如何在插入数据时将数据记录到双向链表中的呢?

答案是:LinkedHashMap重写了父类的addEntry()和createEntry()方法,并在createEntry()方法中将元素插入到双向链表的尾部。

void addEntry(int hash, K key, V value, int bucketIndex) {
//调用父类的addEntry方法
super.addEntry(hash, key, value, bucketIndex);

//实现移除最近最少使用的元素,不过从源码上看,该方法返回的值一直为false
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}

void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);//将刚加入桶中的元素添加到记录插入顺序的双向链表中的尾节点。
size++;
}

//双向链表尾插法
private void addBefore(Entry<K,V> existingEntry) {
after  = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}


五、get()方法

LikedHashMap重写了父类的get()方法,第一步调用父类的getEntry()方法,获取到相应的entry元素,第二步调用重写的recordAccess()方法,来记录访问顺序;

void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}


只有当accessOrder为true时,即指定迭代顺序为访问顺序时,才会将当前元素插入到双向链表的头结点。

六、总结

LinkedHashMap作为HashMap的子类,其实现方式十分相似,区别在于,LinkedHashMap可以保证迭代顺序和插入顺序的一致,当你发现从数据库里读取的数据和返回的数据顺序不一致时 就要考虑使用LinkedHashMap了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java