Java

来源:互联网 发布:旅游saas平台源码 编辑:程序博客网 时间:2024/05/22 15:48

LinkedHashMap源码解析


源码解析对应JDK1.7

JDK1.7源码下载地址:JDK1.7源码下载


先看下LinkedHashMap源码中官方说明:

Hash table and linked list implementation of the <tt>Map</tt> interface, with predictable iteration order. This implementation differs from <tt>HashMap</tt> in that it maintains a doubly-linked list running through all of its entries. 
大致的意思:
Hash表和Map接口的链表实现,具有可预测的迭代顺序。
该实现与HahsMap不同之处在于它保持双向链表运行其所有条目

之前在讲解HahsMap的时候,说过,迭代HashMap的顺序不是HashMap的放置顺序,也就是无序的。
于是LinkedHashMap出现了,通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序
虽然增加了时间和内存空间上的开销



首先记住结论:
LinkedHashMap 允许k,v为null。
LinkedHashMap key不允许重复,value允许重复。
LinkedHashMap 是有序的。
LinkedHashMap 非线程安全。




LinkedHashMap基本数据结构

1. LinkedHashMap 可以认为是HashMap + LinkedList;使用HashMap存储数据,使用LinkedList维护插入元素的先后顺序。
2. LinkedHashMap 实现的基本思想是多态。

我们看下LinkedHashMap类的定义:

public class LinkedHashMap<K, V> extends HashMap<K, V> implements Map<K, V>
LinkedHashMap继承了HashMap,实现了Map接口;
于是LinkedHashMap是HashMap的子类,LinkedHashMap继承了HashMap中所有非private的方法。

我们看下LinkeHashMap中自身的方法


JDK API中的方法


LinkedHashMap中并没有什么操作数据结构的方法,LinkedHashMap操作数据结构和HashMap是一样的,细节上有些不同。


LinkedHashMap 和 HashMap 最大的区别是它们的基本数据结构,我们看下LinkedHashMap的基本数据结构:

/*** LinkedHashMap entry.*/private static class Entry<K, V> extends HashMap.Entry<K, V> {// These fields comprise the doubly linked list used for iteration.// 这些字段包括用于迭代的双向链表。// 这两个字段是LinkedHashMap独有的。// before,after用于维护Entry插入的先后循序。Entry<K, V> before, after;Entry(int hash, K key, V value, HashMap.Entry<K, V> next) {super(hash, key, value, next);}/** * Removes this entry from the linked list.<br> * 从链表中删除此条目。 */private void remove() {before.after = after;after.before = before;}/** * Inserts this entry before the specified existing entry in the * list.<br> * 在列表中指定的现有条目之前插入此条目。 *  */private void addBefore(Entry<K, V> existingEntry) {after = existingEntry;before = existingEntry.before;before.after = this;after.before = this;}/** * This method is invoked by the superclass whenever the value of a * pre-existing entry is read by Map.get or modified by Map.set. If the * enclosing Map is access-ordered, it moves the entry to the end of the * list; otherwise, it does nothing.<br> * 每当Map.get读取预先存在的条目的值或由Map.set修改时,超类就会调用此方法。<br> * 如果封闭的Map是访问排序的,它将该条目移动到列表的末尾; 否则,它什么都不做。<br> */void recordAccess(HashMap<K, V> m) {LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>) m;if (lm.accessOrder) {lm.modCount++;remove();addBefore(lm.header);}}void recordRemoval(HashMap<K, V> m) {remove();}}
罗列下Entry中的属性:
int hash
K key
V value
HashMap.Entry<K, V> next

Entry<K, V> before
Entry<K, V> after


前面四个属性参数是从HashMap.Entry中继承而来;
后两个是LinkedHashMap独有的。

其中next属性是HashMap.Entry中用于维护Entry链表的,不要搞混了。



LinkedHashMap构造方法
LinkedHashMap():
创建一个默认初始容量16和负载因子0.75的空的LinkedHashMap。

LinkedHashMap(int initialCapacity):
创建一个空的LinkedHashMap,指定初始容量和默认负载因子为0.75。

LinkedHashMap(int initialCapacity, float loadFactor):
创建一个空的LinkedHashMap实例,指定初始容量和负载因子。

LinkedHashMap(Map<? extends K, ? extends V> m):
创建一个空的LinkedHashMap,默认初始容量和负载因子,容纳指定集合中的映射。

LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder):
创建空的LinkedHahsMap,指定初始容量,负载因子和访问模式。



LinkedHashMap自带参数

/** * The head of the doubly linked list.<br> * 双向链表的表头元素 */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.<br> * true表示按照访问顺序迭代,false时表示按照插入顺序 *  * @serial */private final boolean accessOrder;




LinkedHashMap常用方法

put()方法
Demo:

public static void main(String[] args) {LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();map.put("a", "a");}
我们先来看构造方法那边,往下看,他是怎么创建LinkedHashMap对象的。

/** * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance * with the default initial capacity (16) and load factor (0.75).<br> * 创建一个默认初始容量16和负载因子0.75的空的LinkedHashMap */public LinkedHashMap() {super();accessOrder = false;}
我们去super()方法里面去看,因为继承了HashMap,所以super()方法是HashMap中的默认构造方法

/*** Constructs an empty <tt>HashMap</tt> with the default initial capacity* (16) and the default load factor (0.75).*/public HashMap() {this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);}
再通过this方法往下看:

/*** 指定容量大小和加载因子的构造函数* * @param initialCapacity* @param loadFactor*/public HashMap(int initialCapacity, float loadFactor) {// 参数校验if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);// 指定容量是否超出最大容量限制校验,如果超出,就用最大容量if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;// 校验加载因子,判断参数是否是"非数字"的值if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " + loadFactor);// Find a power of 2 >= initialCapacity// 找出"大于initialCapacity(指定容量)的最小2的幂"int capacity = 1;while (capacity < initialCapacity)capacity <<= 1;// 加载因子赋值this.loadFactor = loadFactor;// 设置HashMap的阀值,当HashMap中存储的数据量达到threshold时,需要扩容// Math.min():选择一个小的参数threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);// 创建指定长度的table数组(entry)table = new Entry[capacity];useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);init();}
很眼熟吧...HashMap中的构造方法。
注意最后有一个init()方法,在HashMap中是没有做任何处理的,我们使用Eclipse的快捷键(Control + t)点击init()方法。


你看,init()方法,被LinkedHashMap重写了,这就是多态的体现..
我们看看重写了什么...

/*** Called by superclass constructors and pseudoconstructors (clone,* readObject) before any entries are inserted into the map. Initializes the* chain.<br>* 在任何条目插入映射之前,由超类构造函数和伪构造函数(clone,readObject)调用。 初始化链。*/@Overridevoid init() {header = new Entry<>(-1, null, null, null);header.before = header.after = header;}
这个init()方法是LinkedHashMap中的,init()方法之后创建了一个空的LinkedHashMap

我们接着上面Demo往下看,接着就是put元素了
我们在Eclipse按住control,然后单机put(),会发现,进入了HashMap的put()方法
public V put(K key, V value) {// 如果key为空,将null存放在table[0]第一个位置,这就是HashMap允许存null的原因if (key == null)return putForNullKey(value);// 计算key的hash值int hash = hash(key);// 根据hash码和数组长度,计算table数组下标int i = indexFor(hash, table.length);// 从i处开始迭代entry链表,找到key保存的位置for (Entry<K, V> e = table[i]; e != null; e = e.next) {Object k;// 判断该链条上是否有hash值相同的(key相同)// 若存在key相同,直接覆盖value,返回旧的valueif (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;// 取出旧值e.value = value;// 赋新值e.recordAccess(this);return oldValue;// 返回旧值}}// 修改次数+1modCount++;// i处没有entry链表(该位置为空),将key,value添加至i处addEntry(hash, key, value, i);return null;}
看到这个put()方法里面最后有个addEntry()方法吗?
这个方法也是个多态...control + t 可以看到LinkeHashMap实现了它。


我们去LinkedHashMap里面看看..

/*** This override alters behavior of superclass put method. It causes newly* allocated entry to get inserted at the end of the linked list and removes* the eldest entry if appropriate.<br>* * 这个覆盖改变了超类put方法的行为。<br>* 它导致新分配的条目在链接列表的末尾插入,并在适当的情况下删除最老的条目。<br>* */void addEntry(int hash, K key, V value, int bucketIndex) {super.addEntry(hash, key, value, bucketIndex);// Remove eldest entry if instructedEntry<K, V> eldest = header.after;if (removeEldestEntry(eldest)) {removeEntryForKey(eldest.key);}}
if判断里面的语句暂时不看。
我们跟着super.addEntry(hash, key, value, bucketIndex);继续往下看...
返回到了HashMap.Entry中的addEntry中:

void addEntry(int hash, K key, V value, int bucketIndex) {// 首先判断是否需要扩容// 'hashMap的大小' 大于等于 '阀值(加载因子*容量)' && table数组对应下标位置有数据if ((size >= threshold) && (null != table[bucketIndex])) {// 容量扩大两倍resize(2 * table.length);// key为null,hash取0// key不为null,根据key计算hashhash = (null != key) ? hash(key) : 0;// 重新计算哈希码的索引bucketIndex = indexFor(hash, table.length);}// 创建entrycreateEntry(hash, key, value, bucketIndex);}
最后一个方法:createEntry()
猜到了吧,LinkedHashMap重写了它

跟下去看看..

/*** This override differs from addEntry in that it doesn't resize the table* or remove the eldest entry.<br>* 此覆盖与addEntry不同之处在于它不会调整表的大小或删除最老的条目。*/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++;}
前两行代码不用解释吧,看明白HashMap就知道了...
我们看看e.addBefore(header)方法
/*** Inserts this entry before the specified existing entry in the* list.<br>* 在列表中指定的现有条目之前插入此条目。* */// existingEntry表示的是headerprivate void addBefore(Entry<K, V> existingEntry) {// 新增entry的后一个节点(after)=header地址。after = existingEntry;// 新增entry的前一个节点(before)是header的before地址before = existingEntry.before;//header的after此时等于新增元素entrybefore.after = this;//header的before此时等于新增元素entryafter.before = this;}

(感谢原作者的图例)
上面这段代码我看了半天才看明白,后来看了这张图才明白..(建议看不明白的,多看两遍,对着图)


至此,header与新增entry的双向链表就形成了。


get()方法
源码:

/*** Returns the value to which the specified key is mapped, or {@code null}* if this map contains no mapping for the key.<br>* 返回指定键映射到的值,如果此映射不包含该键的映射,则返回{@code null}。** <p>* More formally, if this map contains a mapping from a key {@code k} to a* value {@code v} such that {@code (key==null ? k==null :* key.equals(k))}, then this method returns {@code v}; otherwise it returns* {@code null}. (There can be at most one such mapping.)** <p>* A return value of {@code null} does not <i>necessarily</i> indicate that* the map contains no mapping for the key; it's also possible that the map* explicitly maps the key to {@code null}. The {@link #containsKey* containsKey} operation may be used to distinguish these two cases.*/public V get(Object key) {// HashMap中的方法,根据key获取对应valueEntry<K, V> e = (Entry<K, V>) getEntry(key);// 为空返回nullif (e == null)return null;e.recordAccess(this);return e.value;}
上面几个方法很好理解吧..
看下e.recordAccess(this);
recordAccess,顾名思义,记录访问,就是说你这次访问了双向链表,将它记录下来,怎么记录?
将访问的对象(Entry)移到队列尾部。在HashMap中没有实现,LinkedHashMap对其进行了实现。
/*** This method is invoked by the superclass whenever the value of a* pre-existing entry is read by Map.get or modified by Map.set. If the* enclosing Map is access-ordered, it moves the entry to the end of the* list; otherwise, it does nothing.<br>* 每当Map.get读取预先存在的条目的值或由Map.set修改时,超类就会调用此方法。<br>* 如果封闭的Map是访问排序的,它将该条目移动到列表的末尾; 否则,它什么都不做。<br>*/void recordAccess(HashMap<K, V> m) {LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>) m;if (lm.accessOrder) {lm.modCount++;remove();addBefore(lm.header);}}/*** Removes this entry from the linked list.<br>* 从链表中删除此条目。*/private void remove() {before.after = after;after.before = before;}/*** Inserts this entry before the specified existing entry in the* list.<br>* 在列表中指定的现有条目之前插入此条目。* */// existingEntry表示的是headerprivate void addBefore(Entry<K, V> existingEntry) {// 新增entry的后一个节点(after)=header地址。after = existingEntry;// 新增entry的前一个节点(before)是header的before地址before = existingEntry.before;// header的after此时等于新增元素entrybefore.after = this;// header的before此时等于新增元素entryafter.before = this;}
上面代码看出每次recordAccess的时候做了两件事情:
a. 把待移动的Entry前后Entry想连
b. 把待移动的Entry移动到尾部
这些都是基于accessOrder=true的情况下,看下图(感谢原作者的图)

上面这两个方法捋明白了,其他的方法也就懂了..
下面我们看看LRU算法


LinkedHashMap实现LRU算法缓存

LRU算法是什么?
LRU是" Least Recently Used "的缩写,翻译过来是"最近最少使用"。
也就是说LRU算法会将最近最少使用的缓存移除,让给最新使用的缓存;而往往经常读取的,也就是读取次数最多的。
所以利用好LRU缓存,提高对热点数据的缓存效率及内存使用率。

当缓存满了,会优先淘汰那些最近最不常访问的数据。
举个栗子:
数据a,一天前访问过;数据b,两天前访问过;当缓存满了,优先淘汰数据b。


LRU算法的实现
LRU实现需要那几步?
a. 限制缓存大小
b. 查询出最近最少使用的缓存
c. 给最近最少使用的缓存做一个标识

就如下图:(感谢原作者的图)


我们看下LinkedHashMap有参构造函数方法

/*** Constructs an empty <tt>LinkedHashMap</tt> instance with the specified* initial capacity, load factor and ordering mode.<br>* 创建空的LinkedHahsMap,指定初始容量,负载因子和访问模式** @param initialCapacity*            the initial capacity* @param loadFactor*            the load factor* @param accessOrder*            the ordering mode - <tt>true</tt> for access-order,*            <tt>false</tt> for insertion-order* @throws IllegalArgumentException*             if the initial capacity is negative or the load factor is*             nonpositive*/public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}
最后一个参数accessOrder:
a. false:所有的entry按照插入的顺序排列
b. true:所有的entry按照访问顺序排列

解释下第二个:
如果链表中有1 2 3个entry元素,如果你访问了1,那就把1移动到链表尾部,就是 2 3 1 。
每次访问都是将访问的数据移到链表尾部;
于是每次要淘汰数据的时候,链表头部的那个数据不就是最不常访问的那个数据了嘛...
换句话说,双向链表头部的数据就是要淘汰的数据。

简单实现
当缓存中的数据超出阀值,将最老的数据删除

/*** 基于LinkedHashMap实现LRU算法* * @author CYX** @param <K>* @param <V>*/public class LRULinkedashMap<K, V> extends LinkedHashMap<K, V> {private static final long serialVersionUID = 1L;// 定义缓存容量private int capacity;public LRULinkedashMap(int capacity) {// 创建LinkedHashMap,使用默认参数super(16, 0.75F);// 缓存最大容量this.capacity = capacity;}/** * 实现LRU算法的关键方法,如果LinkedHashMap中的元素大于缓存最大容量,则删除表的顶端元素 */@Overrideprotected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {System.out.println("==== " + eldest.getKey() + " , " + eldest.getValue());return size() > capacity;}}public class TestLRU {public static void main(String[] args) {// 创建LRULinkedashMap对象,并指定缓存容量最大为4Map<Integer, Integer> map = new LRULinkedashMap<Integer, Integer>(4);for (int i = 0; i < 10; i++) {map.put(i, i + 1);}iter(map);}private static void iter(Map<Integer, Integer> map) {for (Entry<Integer, Integer> iterator : map.entrySet()) {System.out.println(iterator.getKey() + " , " + iterator.getValue());}}}输出结果:==== 0 , 1==== 0 , 1==== 0 , 1==== 0 , 1==== 0 , 1==== 1 , 2==== 2 , 3==== 3 , 4==== 4 , 5==== 5 , 66 , 77 , 88 , 99 , 10
解释下:
main()方法中创建LRULinkedashMap对象,设置阀值为4,然后循环放入key为0~9的数据。
启用LRU模式的LinkedHashMap会在每次有新元素加入的时候,判断当前存储元素数量是否超出缓存上线,

也就是执行removeEldestEntry()方法,通过输出结果,我们看到它将6之前的元素全部删除了。


参考资料:

http://www.cnblogs.com/xrq730/p/5052323.html
http://uule.iteye.com/blog/1522291
https://tonydeng.github.io/2015/07/16/linkedhashmap-based-lrucache-implementation/

原创粉丝点击