概述

从名字上看LinkedHashMap相比于HashMap,显然多了链表的实现。从功能上看,LinkedHashMap有序,HashMap无序。这里的顺序指的是添加顺序或者访问顺序。

基本使用

@Test
    public void test1(){
        LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(16, 0.75f, true);

        map.put(1,1);
        map.put(3,3);
        map.put(4,4);
        map.put(2,2);

        for (Map.Entry entry : map.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        map.get(1);
        map.get(2);
        System.out.println();
        for (Map.Entry entry : map.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
    }

我们通过构造函数的第三个参数accessOrder设为true(默认为false),该参数为true,则根据访问顺序排序。如下,当我们调用get()方法的时候,再次遍历发现数据被放到了最后面。

1:1
3:3
4:4
2:2

3:3
4:4
1:1
2:2

源码分析

首先LinkedHashMap继承了HashMap,因此其基本使用与HashMap几乎完全一致。
可以看到内部实现了双向链表 head指向最久访问,tail指向最新访问。

/**
     * The head (eldest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * The tail (youngest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;  // Entry类维护了双线链表所需的前后节点指向
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

既然有了双向链表的基本定义,那么他是怎么实现双向链表的呢?找了这个类,发现并没有重写put方法,说明直接使用父类的put。那么直接看HashMap的putVal()方法。HashMap的源码就不再详细讲了,感兴趣的朋友可以看看我的另一篇文章。
观察HashMap的putVal的时候我们发现创建节点会频繁的使用到newNode()方法。

if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null); //添加节点的时候使用newNode
        else {
           ......省略

我们观察LinkedHashMap,果然,在这里重写了,将每个节点添加到双向链表中。

   Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }
   private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

前面已经看到了put操作,LinkedHashMap在put方法执行的时候维护了一个双向链表。接下来我们看一下get操作。

  public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null) // 调用父类的getNode
            return null;
        if (accessOrder) // accessOrder为true则调整双向链表顺序
            afterNodeAccess(e);
        return e.value;
    }
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) { // accessOrder为true且 尾节点不为当前元素
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

当我们将accessOrder设为true的时候,LinkedHashMap会将当前元素调整到双向链表末尾。

LRU缓存的实现

LRU(Least Recently Used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

根据定义,为了实现LRU,LinkedHashMap我们需要将accessOrder设为true。以保证最新使用的可以调整到链表的末尾。那么另一个比较重要的就是当缓存满的时候,需要淘汰最久没有使用的数据,及链表的头。

因此,该操作应该是存在put方法执行的过程中,由于LinkedHashMap并没有实现put方法,那么继续看HashMap里面的putVal(); 在方法快结束的时候发现了一个有趣的类,从方法名就感觉可以从这里入手。

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true); //最后一个参数即下面的evict为true
    }
   afterNodeInsertion(evict);  // HashMap并没有对这个类进行实现。

查看LinkedHashMap的afterNodeInsertion

    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) { // 我们只需要在这里重写移除最久没有使用的节点的判断条件,就可以移除节点了
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

我们假设LRU里最大容量为16,只需要将构造方法里的accessOrder设为true,并且重写removeEldestEntry的判断条件就可以了。

public class LruLinkedHashMap<K, V> extends LinkedHashMap<K, V> {

    private Integer capacity;

    public LruLinkedHashMap(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }

    public static void main(String[] args) {
        LruLinkedHashMap<Integer, Integer> lru = new LruLinkedHashMap(16);
        for (int i = 0; i < 16; i++) {
            lru.put(i, i);
        }
        System.out.println("当前lru缓存的元素为");
        for (Map.Entry entry : lru.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        lru.get(0);
        lru.get(1);
        System.out.println("当前lru缓存的元素为");
        for (Map.Entry entry : lru.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        lru.put(16, 16);
        lru.put(17, 17);
        System.out.println("当前lru缓存的元素为");
        for (Map.Entry entry : lru.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
    }
}

测试结果如下,最左边的为最近最久未使用。

当前lru缓存的元素为
0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9 10:10 11:11 12:12 13:13 14:14 15:15
当前lru缓存的元素为
2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9 10:10 11:11 12:12 13:13 14:14 15:15 0:0 1:1
当前lru缓存的元素为
4:4 5:5 6:6 7:7 8:8 9:9 10:10 11:11 12:12 13:13 14:14 15:15 0:0 1:1 16:16 17:17 

LinkedHashMap源码分析及实现LRU的更多相关文章

  1. Java集合系列[4]----LinkedHashMap源码分析

    这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...

  2. LinkedHashMap源码分析

    hashMap源码分析:hashMap源码分析 版本说明:jdk1.7LinkedHashMap继承于HashMap,是一个有序的Map接口的实现.有序指的是元素可以按照一定的顺序排列,比如元素的插入 ...

  3. Java 容器 LinkedHashMap源码分析1

    同 HashMap 一样,LinkedHashMap 也是对 Map 接口的一种基于链表和哈希表的实现.实际上, LinkedHashMap 是 HashMap 的子类,其扩展了 HashMap 增加 ...

  4. 死磕 java集合之LinkedHashMap源码分析

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 LinkedHashMap内部维护了一个双向链表,能保证元素按插入的顺序访问,也能以访问 ...

  5. java Linkedhashmap源码分析

    LinkedHashMap类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是插入次序,或者是最近最少使用(LRU)的次序.只比HashMap慢一点:而在迭代访问时反而更快,因为它使用链表维 ...

  6. Java8集合框架——LinkedHashMap源码分析

    本文的结构如下: 一.LinkedHashMap 的 Javadoc 文档注释和简要说明 二.LinkedHashMap 的内部实现:一些扩展属性和构造函数 三.LinkedHashMap 的 put ...

  7. Java集合之LinkedHashMap源码分析

    概述 HashMap是无序的, 即put的顺序与遍历顺序不保证一样. LinkedHashMap是HashMap的一个子类, 它通过重写父类的相关方法, 实现自己的功能. 它保留插入的顺序. 如果需要 ...

  8. LinkedHashMap 源码分析

    LinkedHashMap LinkedHashMap 能解决什么问题?什么时候使用 LinkedHashMap? 1)LinkedHashMap 按照键值对的插入顺序进行遍历,LinkedHashM ...

  9. HashMap LinkedHashMap源码分析笔记

    MapClassDiagram

随机推荐

  1. 【Spring Cloud笔记】Eureka注册中心增加权限认证

    在Spring Cloud通过Eureka实现服务注册与发现时,默认提供web管理界面,但是如果在生产环境暴露出来,会存在安全问题.为了解决这个问题,我们可以通过添加权限认证进行控制,具体步骤如下: ...

  2. 使用Navicat快速生成MySQL数据字典

    1.通过information_schema.COLUMNS表 查询该表可得到所需字段信息 SELECT * FROM information_schema.COLUMNS; 如下图所示: 2.示例 ...

  3. 记录Html+Css流程表格

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. 关于Knowledge Transfer的一点想法

    维基百科中对于Knowledge Transfer(知识转移)的定义是: 知识转移是指分享或传播知识并为解决问题提供投入.在组织理论中,知识转移是将知识从组织的一个部分转移到另一个部分的实践问题. 与 ...

  5. python学习笔记5_异常

    python学习笔记5_异常 1.什么事异常 Python使用异常对象(exception object) 来表示异常情况.遇到错误会发生异常. 如果异常对象未被处理或被捕捉,程序就会用所谓的回溯(t ...

  6. Python--day10(函数(使用、分类、返回值))

    1.  函数 1.  函数: 完成特定功能的代码块,作为一个整体,对其进行特定的命名,该名字就代表这函数 现实中:很多问题要通过一些工具进行处理 => 可以将工具提前生产出来并命名 =>通 ...

  7. fuel一键部署openstack

    一.安装环境: 1. 所需物理主机的要求如下 内存:8GB+,推荐16GB:(少于8GB的就免谈了) 磁盘:500GB+: 物理机OS:ubuntu-desktop-amd64 14.04(推荐) 或 ...

  8. 手动执行脚本可以运行,crontab自动执行无效的解决方法

    在需要执行的脚本里加入环境变量即可,如下图:

  9. 算法01 C语言设计

    8.21 #include <stdio.h> void bubbleSort(int **p, int n); int main(void){ int a[100]; int *b[10 ...

  10. Cnario Player 接入视频采集卡采集外部音视频信号测试

    测试产品 型号: TC-D56N1-30P采集卡 参数: 1* HDMI 1.4输入, PCIe 接口为PCI-Express x4(Gen2), 最高支持4096x2160@30Hz, 支持1920 ...