一.概述:

 LinkedHashMap是HashMap的子类,它的基本操作与HashMap相同,与之不同的是,它可以实现按照插入顺序进行排序.也可以通过在构造函数中指定参数,按照访问顺序排序(LinkedHashSet无法按照访问顺序进行排序).

二.LinkedHashMap是如何实现按照插入顺序进行排序的?
  LinkedHashMap在Entry内部类中动了一点小手脚…实际上,LinkedHashMap所有的元素可以构成一个以header(Entry对象)为头的双向链表.在HashMap中有一个钩子方法init,在构造函数最后一行调用,在HashMap的实现中,init方法没有实现任何内容.而LinkedHashMap则实现了init方法,init方法的代码如下:

  1. void init() {
  2. header = new Entry<K,V>(-1, null, null, null);
  3. header.before = header.after = header;
  4. }

  LinkedHashMap对addEntry方法也做出了修改,首先它没有改变数组的数据结构,在放入一个元素的时候,依然将元素的hash值对应的索引处的引用指向该元素,将该元素的next属性指向之前该索引处引用指向的元素,不同的是,在插入一个新的元素的时候,它还维护了双向链表数据结构的插入.首先看看内部类Entry的数据结构:

  1. private static class Entry<K,V> extends HashMap.Entry<K,V> {
  2. // 这两个引用说明entry之间是用双向链表连接起来的
  3. Entry<K,V> before, after;
  4.  
  5. Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
  6. super(hash, key, value, next);
  7. }
  8.  
  9. /**
  10. * 将这个entry从双向链表中移除
  11. */
  12. private void remove() {
  13. before.after = after;
  14. after.before = before;
  15. }
  16.  
  17. /**
  18. * 将这个entry加到给定参数entry的前面
  19. */
  20. private void addBefore(Entry<K,V> existingEntry) {
  21. after = existingEntry;
  22. before = existingEntry.before;
  23. before.after = this;
  24. after.before = this;
  25. }
  26.  
  27. /**
  28. * 这个方法调用在当支持按照访问次数排序的时候.也是在HashMap中定义,
  29. 但没有给出具体方法,在LinkedHashMap中实现
  30. */
  31. void recordAccess(HashMap<K,V> m) {
  32. LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
  33. if (lm.accessOrder) {
  34. lm.modCount++;
  35. remove();
  36. addBefore(lm.header);
  37. }
  38. }
  39.  
  40. void recordRemoval(HashMap<K,V> m) {
  41. remove();
  42. }
  43. }

   再看看addEntry方法和createEntry方法:

  1. void addEntry(int hash, K key, V value, int bucketIndex) {
  2. createEntry(hash, key, value, bucketIndex);
  3. // 在LinkedHashMap中,removeEldestEntry方法始终返回false
  4. Entry<K,V> eldest = header.after;
  5. if (removeEldestEntry(eldest)) {
  6. removeEntryForKey(eldest.key);
  7. } else {
  8. if (size >= threshold)
  9. resize(2 * table.length);
  10. }
  11. }
  12. void createEntry(int hash, K key, V value, int bucketIndex) {
  13. HashMap.Entry<K,V> old = table[bucketIndex];
  14. Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
  15. table[bucketIndex] = e;
  16. //维护双向链表的操作
  17. e.addBefore(header);
  18. size++;
  19. }

  可以看出调用的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定义的迭代器:

  1. private abstract class LinkedHashIterator<T> implements Iterator<T> {
  2. Entry<K,V> nextEntry = header.after;
  3. Entry<K,V> lastReturned = null;
  4.  
  5. int expectedModCount = modCount;
  6.  
  7. public boolean hasNext() {
  8. return nextEntry != header;
  9. }
  10.  
  11. public void remove() {
  12. if (lastReturned == null)
  13. throw new IllegalStateException();
  14. if (modCount != expectedModCount)
  15. throw new ConcurrentModificationException();
  16.  
  17. LinkedHashMap.this.remove(lastReturned.key);
  18. lastReturned = null;
  19. expectedModCount = modCount;
  20. }
  21.  
  22. Entry<K,V> nextEntry() {
  23. if (modCount != expectedModCount)
  24. throw new ConcurrentModificationException();
  25. if (nextEntry == header)
  26. throw new NoSuchElementException();
  27.  
  28. Entry<K,V> e = lastReturned = nextEntry;
  29. nextEntry = e.after;
  30. return e;
  31. }
  32. }

  可以看出,迭代器是从header开始,不断调用header.after获取元素.直到指针又重新回到header.因此这样子的迭代顺序配合内部双向链表的数据结构保证了实现了按照插入顺序排序.
三.LinkedHashMap是如何实现按照访问次序排序的?
  在LinkedHashMap中,有一个成员变量accessOrder来判断是否需要按照访问顺序排序.accessOrder可以通过,在构造函数中传入true,来指定,必须按照访问顺序排序.所谓的按照访问顺序排序是指,如果一对键值对被访问了,那么就将它移到当前遍历顺序的最后,这样,最不经常访问的键值对会排在前面.
  在LinkedHashMap中,通过在get后,调用recordAccess方法,来实现按照访问顺序排序:

  1. public V get(Object key) {
  2. Entry<K,V> e = (Entry<K,V>)getEntry(key);
  3. if (e == null)
  4. return null;
  5. e.recordAccess(this);
  6. return e.value;
  7. }

  而recordAccess方法在entry内部类中实现,它是将该该entry移除双向链表,并且将该entry重新加入到header的前面(遍历时将会最后一个遍历),这样就实现了按照访问次序排序.

  

Java源码初学_LinkedHashMap的更多相关文章

  1. Java源码初学_AbstractList&AbstractCollection

    一.AbstractCollection抽象类:(提供了Collection接口的骨干实现,以减少实现接口所需要的工作) 1.contains方法 contains方法,通过迭代器对于列表的每一个元素 ...

  2. Java源码初学_HashSet&LinkedHashSet

    一.概述 HashSet是建立在HashMap的基础上的,其内部存在指向一个HashMap对象的引用,操作HashSet实际上就是操作HashMap,而HashMap中所有键的值都指向一个叫做Dumm ...

  3. Java源码初学_HashMap

    一.概念 HashMap的实例有两个参数影响其性能:初始容量和加载因子.容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量.加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度.当哈希表 ...

  4. Java源码初学_LinkedList

    一.LinkedList的内部数据结构 LinkedList底层是一个链表的数据结构,采用的是双向链表,基本的Node数据结构代码如下: private static class Node<E& ...

  5. Java源码初学_ArrayList

    一.ArrayList的构造器和构造方法 在ArrayList中定义了两个空数组,分别对应当指定默认构造方法时候,指向的数组已经给定容量,但是容量等于0的时候,指向的数组.此外在构造函数中传入Coll ...

  6. 如何阅读Java源码 阅读java的真实体会

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心.   说到技术基础,我打个比 ...

  7. Android反编译(一)之反编译JAVA源码

    Android反编译(一) 之反编译JAVA源码 [目录] 1.工具 2.反编译步骤 3.实例 4.装X技巧 1.工具 1).dex反编译JAR工具  dex2jar   http://code.go ...

  8. 如何阅读Java源码

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动.源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧, ...

  9. Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库

    http://www.iteye.com/topic/1113732 原则网址 Java源码初接触 如果你进行过一年左右的开发,喜欢用eclipse的debug功能.好了,你现在就有阅读源码的技术基础 ...

随机推荐

  1. js 对象toString()方法

    ({}+{}).length == 30; ({}).toString() '[object Object]' 当对象需要调用toString()方法时会被自动调用.

  2. JAVA字段的初始化规律

    JAVA字段的初始化规律 1.类的构造方法 (1)“构造方法”,也称为“构造函数”,当创建一个对象时,它的构造方法会被自动调用.构造方法与类名相同,没有返回值. (2)如果类没有定义构造函数,Java ...

  3. SDUT 2413:n a^o7 !

    n a^o7 ! Time Limit: 1000MS Memory limit: 65536K 题目描述 All brave and intelligent fighters, next you w ...

  4. java中@value的环境配置

    @value 在现阶段我想大家对注解都不陌生,@value的用法就是在后台获取配置文件的信息,从而方便修改一些固定的配置.不明白的可以百度@value的详解. 配置@value有以下几个步骤. 1.首 ...

  5. js 正则表达式中的惰性匹配

    今天看到了一个正则的问题,在其实使用了如下的符号: var reg = /\{(.+?)\}/g; 其中的?号让我疑惑了很久,其实他在这里是惰性匹配的意思,就是能匹配的尽量少匹配.相反,如果不加这个? ...

  6. 20150916_001 vba 基础

    一.什么是“宏”.“宏”有什么用 关于“宏”的详细定义,可以参考百度百科的解释(点击查看).我给它一个简单的或许不太严谨的定义: 宏的通俗定义:宏是被某些软件所能识别.理解并执行的特定代码/脚本. 宏 ...

  7. Uva 12563,劲歌金曲,01背包

    题目链接:https://uva.onlinejudge.org/external/125/12563.pdf 题意:n首歌,每首歌的长度给出,还剩 t 秒钟,由于KTV不会在一首歌没有唱完的情况下切 ...

  8. DLL学习笔记一(DLL导入导出)

    创建DLL: 先声明导出函数:使用__declspec(dllexport) #include"DLLSample.h" #ifndef _DLL_SAMPLE_H #define ...

  9. ubuntu APT-GET工作原理

    转 http://kurenai.elastos.org/2013/05/02/ubuntu-apt-get%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/   先介绍几个和 ...

  10. Struts+Hibernate+jsp页面 实现分页

    dao层数据库代码: package com.hanqi.dao; import java.util.ArrayList; import java.util.List; import org.hibe ...