Java容器类浅析三-----保证插入顺序的HashMap--LinkedHashMap的存取原理
2016-06-15 18:04
453 查看
上一节我们说到HashMap的存取原理,并一步步地分析了其主要的成员变量、构造函数以及体现其算法的put,get方法。本文将继续分析HashMap的子类—LindedHashMap,来解决上一节提到的,如何保证迭代顺序和插入顺序的一致性的问题。
LinkedHashMap直接继承于HashMap,且其内部属性和存取算法与HashMap基本一致。
那么为什么LinkedHashMap可以做到保证迭代顺序和插入顺序一致呢? 这就要归功于LinkedHashMap中的一个数据结构—Entry header了,header是一个双向链表中的首节点,该双向链表保存了entry插入的顺序,在迭代时,实际上是在迭代该双向链表。每次调用put()方法时,第一步和HashMap的put()方法一致,第二步就是将该元素的before节点指向header的before节点,after节点指向header,将header的before节点指向该元素,以完成双向链表的记录顺序。
双向链表的头结点。
标识迭代顺序的标志,true表示按照访问顺序进行迭代(),false表示按照插入的顺序进行迭代。
构造函数和HashMap的基本一致,只是多了将accessOrder设为false的操作;而且可以将一个HashMap的对象强转为LinkedHashMap的对象。
而且,LinkedHashMap重写了父类的钩子函数–init(),将双向链表的头结点初始化,并将其before、after节点指向了自己,其实就是对双向链表进行初始化。
答案是:LinkedHashMap重写了父类的addEntry()和createEntry()方法,并在createEntry()方法中将元素插入到双向链表的尾部。
只有当accessOrder为true时,即指定迭代顺序为访问顺序时,才会将当前元素插入到双向链表的头结点。
一、概述
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对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树