jdk源码分析之LinkedHashMap
2016-06-02 11:11
721 查看
基本原理
LinkedHashMap继承自HashMap,因此具有HashMap的所有特性。在HashMap的基础上,保持了key的插入顺序或者访问顺序。实现方法就是用双向循环链表将所有的entry链接起来,读取数据的时候直接读取此双向循环链表的数据即可。public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
/** * The head of the doubly linked list. */ private transient Entry<K,V> header; /** * The iteration ordering method for this linked hash map: * true for access-order,false for insertion-order. */ private final boolean accessOrder;
header是双向循环链表的头结点,不存储数据,用于定位头尾节点
header.after指向链表的第一个节点
header.before指向链表的最后一个节点
accessOrder是一个标志位,true的话表示按照访问顺序访问链表,可据此构建LRU缓存,fasle的话表示按照插入顺序访问链表
双向循环链表的Entry节点
LinkedHashMap的Entry继承自HashMap的Entryprivate static class Entry<K,V> extends HashMap.Entry<K,V>
并添加了指向前序节点的前序指针和指向后继节点的后继指针
// These fields comprise the doubly linked list used for iteration. Entry<K,V> before, after;
因此LinkedHashMap的Entry节点具有三个指针域,next指针维护Hash桶中冲突key的链表,before和after维护双向循环链表
为了维护双向循环链表,Entry新增加了4个方法
删除节点remove
/** * Removes this entry from the linked list. */ private void remove() { before.after = after; after.before = before; }
充分体现了链表这种数据结构删除中间节点的方便之处,仅仅修改指针的指向即可。
如果要删除当前节点,就把当前节点的前序节点的后继指针指向当前节点的后继节点,当前节点的后继节点的前序指针指向当前节点的前序节点即可,这样当前节点就从链表中脱离开了,断开的节点也得到重新链接。
添加节点
/** * Inserts this entry before the specified existing entry in the list. */ private void addBefore(Entry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; }
添加节点同样是指针操作,非常高效方便
记录访问recordAccess
/** * 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. */ void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } }
当LinkedHashMap的标志位accessOrder为true时,标志着要采用访问顺序访问链表。
remove(); addBefore(lm.header);
最新访问的节点先从原来的位置删除,然后重现添加到链表的末尾,这样最近最少访问的节点就被挪到了链表的前端。
LinkedHashMap添加数据
LinkedHashMap本质上也是一个HashMap,在HashMap的基础上添加所有entry构成双向循环链表的功能。因此LinkedHashMap并没有覆写HashMap的put方法,只是覆写了HashMap中put方法调用的addEntry方法。先来回顾下HashMap的put方法够干了什么
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
HashMap的put方法主要功能是计算对应key的桶的位置,遍历桶中链表,找到对应key的entry修改原数据。遍历链表结束没有找到对应key的entry则调用addEntry方法将新添加的键值对entry添加到桶中entry链表的头部。
而对于LinkedHashMap中的entry节点来说,要维持两个链表,一个是桶中的next指针域链接的hask冲突的key构成的entry单链表,第二个就是维护所有entry构成的双向循环链表
LinkedHashMap的addEntry方法如下所示
void addEntry(int hash, K key, V value, int bucketIndex) { createEntry(hash, key, value, bucketIndex); // Remove eldest entry if instructed, else grow capacity if appropriate Entry<K,V> eldest = header.after; if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } else { if (size >= threshold) resize(2 * table.length); } }
先调用createEntry把新节点链接到两条链表上,然后判断是否要删除最近最久未访问的节点(也就是双向循环链表的第一个节点),要删除的话就不需要检查是否需要扩容了,都则要检查是否需要扩容。
void createEntry(int hash, K key, V value, int bucketIndex) { HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<K,V>(hash, key, value, old); table[bucketIndex] = e; e.addBefore(header); size++; }
可见新节点被两条链表都链接上了
第一条是单项非循环链表,桶中hash值冲突得key构成的entry链表
HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<K,V>(hash, key, value, old); table[bucketIndex] = e;
第二条是所有entry节点构成的双向循环链表
e.addBefore(header);
containsValue方法直接在双向循环链表中查找值
public boolean containsValue(Object value) { // Overridden to take advantage of faster iterator if (value==null) { for (Entry e = header.after; e != header; e = e.after) if (e.value==null) return true; } else { for (Entry e = header.after; e != header; e = e.after) if (value.equals(e.value)) return true; } return false; }
根据传入参数value是否为null,对双向循环链表进行遍历查找
相关文章推荐
- JDK动态代理VS CgLib
- Ubuntu 安装 JDK 问题
- jdk与jre的区别 很形象,很清晰,通俗易懂
- jdk中String类设计成final的原由
- win7下安装 JDK 基本流程
- jdk环境变量配置
- win2003 jsp运行环境架设心得(jdk+tomcat)
- windows linux jdk安装配置方法
- Java编程之jdk1.4,jdk1.5和jdk1.6的区别分析(经典)
- 详解JDK 5 Annotation 注解之@Target的用法介绍
- 简单记录Cent OS服务器配置JDK+Tomcat+MySQL
- Android开发的IDE、ADT、SDK、JDK、NDK等名词解释
- Java4Android开发教程(一)JDK安装与配置
- Eclipse配置Tomcat和JDK步骤图解
- java中sdk与jdk的区别详细解析
- 简单谈谈JVM、JRE和JDK的区别与联系
- JDK 5 提供的注解:Target、Inherited和Documented的区别
- jdk中密钥和证书管理工具keytool常用命令详解
- 在RedHat系统上安装JDK与Tomcat的步骤
- 在Ubuntu系统下安装JDK和Tomcat的教程