Java源码初学_LinkedHashMap
一.概述:
LinkedHashMap是HashMap的子类,它的基本操作与HashMap相同,与之不同的是,它可以实现按照插入顺序进行排序.也可以通过在构造函数中指定参数,按照访问顺序排序(LinkedHashSet无法按照访问顺序进行排序).
二.LinkedHashMap是如何实现按照插入顺序进行排序的?
LinkedHashMap在Entry内部类中动了一点小手脚…实际上,LinkedHashMap所有的元素可以构成一个以header(Entry对象)为头的双向链表.在HashMap中有一个钩子方法init,在构造函数最后一行调用,在HashMap的实现中,init方法没有实现任何内容.而LinkedHashMap则实现了init方法,init方法的代码如下:
void init() {
header = new Entry<K,V>(-1, null, null, null);
header.before = header.after = header;
}
LinkedHashMap对addEntry方法也做出了修改,首先它没有改变数组的数据结构,在放入一个元素的时候,依然将元素的hash值对应的索引处的引用指向该元素,将该元素的next属性指向之前该索引处引用指向的元素,不同的是,在插入一个新的元素的时候,它还维护了双向链表数据结构的插入.首先看看内部类Entry的数据结构:
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// 这两个引用说明entry之间是用双向链表连接起来的
Entry<K,V> before, after; Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
} /**
* 将这个entry从双向链表中移除
*/
private void remove() {
before.after = after;
after.before = before;
} /**
* 将这个entry加到给定参数entry的前面
*/
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
} /**
* 这个方法调用在当支持按照访问次数排序的时候.也是在HashMap中定义,
但没有给出具体方法,在LinkedHashMap中实现
*/
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();
}
}
再看看addEntry方法和createEntry方法:
void addEntry(int hash, K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);
// 在LinkedHashMap中,removeEldestEntry方法始终返回false
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
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++;
}
可以看出调用的addBefore方法实现了维护双向链表的操作,下面列出了向一个没有任何元素的LinkedHashMap中插入元素时,内部发生的事情:
1.首先计算键值对的key值的hash值.根据hash值计算索引.
2.用传入的键值对new出一个Entry对象.由于索引处是null,那么将底层数组该索引处的引用指向该Entry对象
3.将该entry对象插入到header的前面,这时header.before是该entry,header.after也是该entry.
4.增加size,如果size>=threshold,增加容器的容积.
当继续添加元素的时候,新添加的元素都是在添加到header的前面,而header.after始终是第一个添加的元素.
下面是LinkedHashMap定义的迭代器:
private abstract class LinkedHashIterator<T> implements Iterator<T> {
Entry<K,V> nextEntry = header.after;
Entry<K,V> lastReturned = null; int expectedModCount = modCount; public boolean hasNext() {
return nextEntry != header;
} public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException(); LinkedHashMap.this.remove(lastReturned.key);
lastReturned = null;
expectedModCount = modCount;
} Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == header)
throw new NoSuchElementException(); Entry<K,V> e = lastReturned = nextEntry;
nextEntry = e.after;
return e;
}
}
可以看出,迭代器是从header开始,不断调用header.after获取元素.直到指针又重新回到header.因此这样子的迭代顺序配合内部双向链表的数据结构保证了实现了按照插入顺序排序.
三.LinkedHashMap是如何实现按照访问次序排序的?
在LinkedHashMap中,有一个成员变量accessOrder来判断是否需要按照访问顺序排序.accessOrder可以通过,在构造函数中传入true,来指定,必须按照访问顺序排序.所谓的按照访问顺序排序是指,如果一对键值对被访问了,那么就将它移到当前遍历顺序的最后,这样,最不经常访问的键值对会排在前面.
在LinkedHashMap中,通过在get后,调用recordAccess方法,来实现按照访问顺序排序:
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
而recordAccess方法在entry内部类中实现,它是将该该entry移除双向链表,并且将该entry重新加入到header的前面(遍历时将会最后一个遍历),这样就实现了按照访问次序排序.
Java源码初学_LinkedHashMap的更多相关文章
- Java源码初学_AbstractList&AbstractCollection
一.AbstractCollection抽象类:(提供了Collection接口的骨干实现,以减少实现接口所需要的工作) 1.contains方法 contains方法,通过迭代器对于列表的每一个元素 ...
- Java源码初学_HashSet&LinkedHashSet
一.概述 HashSet是建立在HashMap的基础上的,其内部存在指向一个HashMap对象的引用,操作HashSet实际上就是操作HashMap,而HashMap中所有键的值都指向一个叫做Dumm ...
- Java源码初学_HashMap
一.概念 HashMap的实例有两个参数影响其性能:初始容量和加载因子.容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量.加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度.当哈希表 ...
- Java源码初学_LinkedList
一.LinkedList的内部数据结构 LinkedList底层是一个链表的数据结构,采用的是双向链表,基本的Node数据结构代码如下: private static class Node<E& ...
- Java源码初学_ArrayList
一.ArrayList的构造器和构造方法 在ArrayList中定义了两个空数组,分别对应当指定默认构造方法时候,指向的数组已经给定容量,但是容量等于0的时候,指向的数组.此外在构造函数中传入Coll ...
- 如何阅读Java源码 阅读java的真实体会
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比 ...
- Android反编译(一)之反编译JAVA源码
Android反编译(一) 之反编译JAVA源码 [目录] 1.工具 2.反编译步骤 3.实例 4.装X技巧 1.工具 1).dex反编译JAR工具 dex2jar http://code.go ...
- 如何阅读Java源码
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动.源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧, ...
- Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库
http://www.iteye.com/topic/1113732 原则网址 Java源码初接触 如果你进行过一年左右的开发,喜欢用eclipse的debug功能.好了,你现在就有阅读源码的技术基础 ...
随机推荐
- js比typeof更准确的验证类型方法
var type = function (o){ var s = Object.prototype.toString.call(o); return s.match(/\[object (.*?)\] ...
- word 排版问题
1.wps word为何设置了页边距后下面的页边距不变呢 在章节选项卡中,查看章节导航,有可能是文档分节了,光标所在的节已经调整,而你看到页是另一节 2.分栏 选中你要进行分栏的内容,进行分栏,也可以 ...
- python8
编译和解释性语言的区别 编译 典型的C C++ 编译完成之后是可执行文件. 机器码-底层(外文书,但是不懂中文,翻译成中文就可以看) 解释性语言----看一句英文书,让翻译解释一句 解释器写的代码便于 ...
- Quick-cocos2d-x v3.3 SocketTCP链接(转)
Quick-Cocos2d-x v3.3里面提供了两种长连接WebSockets.SocketTCP,这里说一下SocketTCP的用法. 1 2 3 local net = require(&quo ...
- 【转】SVN 查看历史信息
转载地址:http://lee2013.iteye.com/blog/1074457 SVN 查看历史信息 通过svn命令可以根据时间或修订号去除过去的版本,或者某一版本所做的具体的修改.以下四个命令 ...
- html移动开发app-framework2.0使用心得
1.页面切换动画结束时卡(禁用动画) 2.搜索或导航标签需要固定(标签选择器动态修改高度) 3.pancel容器默认生成的时候内容不放 通过动态的的$("").empty().ht ...
- c# 客户端
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- Animation在每一帧中的执行顺序测试
测试代码: void Update() { transform.position = Vector3.zero; } void LateUpdate() { Debug.Log(transform.p ...
- CSUFT 1005 Coffin Tiles
1005: Coffin Tiles Time Limit: 1 Sec Memory Limit: 128 MB Submit: 2 Solved: 2 Description ...
- python中zip函数
zip函数接受任意多个(包括0个和1个)序列作为参数,返回一个tuple列表.(在海豚实习时自己写了一个要用到zip的函数,那个例子非常代表性) 示例1 for i,j in zip(range(3) ...