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的前面(遍历时将会最后一个遍历),这样就实现了按照访问次序排序.

  

时间: 2024-10-29 05:11:48

Java源码初学_LinkedHashMap的相关文章

Java源码初学_AbstractList&amp;AbstractCollection

一.AbstractCollection抽象类:(提供了Collection接口的骨干实现,以减少实现接口所需要的工作) 1.contains方法 contains方法,通过迭代器对于列表的每一个元素进行遍历,并且判断是否与给定的元素相等.另外由于传入的元素可能为null,因此在执行传入的元素的equals方法的时候,需要先判断是否为null.源代码如下: public boolean contains(Object o) { Iterator<E> it = iterator(); if (

Java源码初学_HashSet&amp;LinkedHashSet

一.概述 HashSet是建立在HashMap的基础上的,其内部存在指向一个HashMap对象的引用,操作HashSet实际上就是操作HashMap,而HashMap中所有键的值都指向一个叫做Dummy value的值.这是一个Object对象.是在HashSet进行显示初始化的时候初始化出来的,而HashMap在构造函数中被创建出来.二.HashMap在HashSet方法中的使用及LinkedHashMap HashSet是建立在HashMap的基础上的,它的几乎所有的操作都是通过HashMa

Java源码初学_LinkedList

一.LinkedList的内部数据结构 LinkedList底层是一个链表的数据结构,采用的是双向链表,基本的Node数据结构代码如下: private static class Node<E> { E item; //节点放置的元素 Node<E> next; //下一节点 Node<E> prev; //上一结点 Node(Node<E> prev, E element, Node<E> next) { this.item = element

Java源码初学_ArrayList

一.ArrayList的构造器和构造方法 在ArrayList中定义了两个空数组,分别对应当指定默认构造方法时候,指向的数组已经给定容量,但是容量等于0的时候,指向的数组.此外在构造函数中传入Collection对象的时候,直接调用对象的toArray方法,并且将容器内部的引用指向得到的数组.源代码如下: private static final long serialVersionUID = 8683452581122892189L; /** * 默认的数组的容量 */ private sta

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

刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧,如果你从来没有学过Java,或是任何一门编程语言如C++,一开始去啃<Core Java>,你是很难从中吸收到营养的,特别是<深入Java虚拟机>这类书,别人觉得好,未必适合现在的你. 虽然Tomcat的源码很漂亮,但我绝不建议你一开始就读它.我文中会专门谈到这个,暂时不展开. 强烈

[笔记&amp;轮子]java源码 生成本地javadoc api文档

在用Eclipse写java代码时候,有时候因为不知道一个java函数的作用,会通过把鼠移动到java函数上,如果它有javadoc的相关内容就会显示出来.但是并非所有java代码都有javadoc:即使安装了javadoc,在eclipse中如果不进行设定,也可能无法使用. 我在win7下安装的是javase的jdk,发现eclipse中默认的javadoc路径是http://download.oracle.com/javase/7/docs/api/,显然这是一个在线资源,问题是网络总是不稳

一个android dex 转java源码工具

和dex2jar,smali2java比起来,这个工具至少结果是正确的,前两者对于循环,异常等的处理明显逻辑就是错误的. 该小工具是基于androguard制作,本来是想自己写一个,后来一找居然有现成的可以利用就简单添加功能. 用法如下 dexdecompile.exe dexfilename outputdirectory 比如 dexdecompile.exe  c:\classes.dex c:\test 运行完毕以后将会在c:\test生成所需要的java源码. 下载地址:百度网盘地址

Java源码转C#源码的五款最佳工具

Java源码转C#源码的五款最佳工具 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 出于某些需要,你可能会遇到把Java源码转换成C#源码的任务.如果是自己一边理解源码,再一边手工翻译,那效率肯定是很低的.有鉴于此,本文推荐了五款最佳的源码转换工具,以解决你的烦恼.工具1#:Java语言转换器助手地址:http://www.microsoft.com/en-us/download/details.aspx?id=14349 Java语言转换器助手是

Java源码阅读的真实体会

原文:http://zwchen.iteye.com/blog/1154193 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧,如果你从来没有学过Java,或是任何一门编程语言如C++,一开始去啃<Core Java>,你是很难从中吸收到营养的,特别是<深入Java虚拟机>这类书,别人觉得好,未必适合现在的你. 虽然Tomcat的