LRU算法实现,HashMap与LinkedHashMap源码的部分总结

关于HashMap与LinkedHashMap源码的一些总结

  • JDK1.8之后的HashMap底层结构中,在数组(Node<K,V> table)长度大于64的时候且链表(依然是Node)长度大于8的时候,链表在转换为红黑树时,链表长度小于等于6时将不会进行转化为红黑树。目的是为了保证效率。其中链表的结点只有next,LinkedHashMap是在Entry<K,V>中添加before, after(双向链表的定义),保证可迭代,遍历时为存入顺序。
  • 下面是LinkedHashMap中的双向链表定义
    //HashMap方法
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
    //LinkedHashMap----------------------------------------
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
    //添加结点,添加次序为从右到左,移动last指针
        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;
        }
    }
  • LinkedHashMap中有个布尔值accessOrder可以改变访问顺序(get),默认是false,不改变,true则把访问的键值对放到尾部,是实现LRU的关键,且这个值在构造方法中可以获得。
  • 0.75f为默认加载因子,若用户自定义size容量大于capacity*0.75(积为threshold)时,数组就会进行扩容,加载不宜太大太小,太大容易哈希冲突,太小浪费空间
  • 关于removeEldestEntry方法:默认返回false,若为true则会执行以下源码摘除头结点
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            //拿到最右边key - 最早添加的数据key
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }
  • 但是两者的线程均不安全没有加同步锁(synchronized),而HashTable是安全的

HashMap实现LRU


  • 主要思路是:常命中、使用多、和刚添加的移至头部,缓存满了先淘汰尾部
  • 最近最久未使用 - 这里指的是Node,需要变换的也是Node,key只作为索引不考虑交换
  • tail和head结点用于摘除结点,其中并不包含任何数据

初始化结点


//定义双列集合的结点结构(双向链表的结点)
    private class Node {
        //key的作用是cache满的时候,hashmap便于淘汰尾部和移除操作,还有遍历
        int key;
        int value;
        Node pre;
        Node post;

        //构造方法初始化Node数据
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }

        public Node() {

        }
    }

定义缓存中的成员变量


    //定义头尾结点
    private Node head;
    private Node tail;
    //定义当前缓存大小
    private int count;
    //定义总缓存大小
    private int size;
    //定义双列集合存储数据
    private HashMap<Integer, Node> cache;

    //构造方法初始化数据
    public LRUCache(int size) {
        //双向链表初始化
        head = new Node();
        tail = new Node();
        //结点外的指针置空
        head.pre = null;
        tail.post = null;
        //头尾结点的互连
        head.post = tail;
        tail.pre = head;

        //容量初始化
        this.count = 0;
        this.size = size;
        cache = new HashMap<>(size);
    }

访问缓存内容方法


//get方法得到key中缓存的数据
    public int get(int key) {
        //取得hashmap中的结点数据
        Node node = cache.get(key);
        //如果没有返回-1
        if (node == null)
            return -1;

        //有,访问后将结点移动到开头,成为最近使用结点
        moveToHead(node);
        //并返回查询的值
        return node.value;
    }

当前结点前移至头结点之后


    //摘除双链表结点
    private void removeNode(Node node) {
        node.pre.post = node.post;
        node.post.pre = node.pre;
    }

    //结点插入头部之后
    private void insertNode(Node node) {

        //前连插入结点
        node.pre = head;
        node.post = head.post;
        //后连插入结点
        head.post.pre = node;
        head.post = node;
    }
    private void moveToHead(Node node) {

        //摘除结点
        removeNode(node);
        //插入头结点之后
        insertNode(node);

    }

调进方法


//put方法存入数据,同时将值放入hashmap的node
    public void put(int key, int value) {
        //获取Node仓库
        Node node = cache.get(key);
        //如果没有命中就调进
        if (node == null) {
            //如果cache满了淘汰尾部
            if (count >= size) {
                cache.remove(tail.pre.key);
                //摘除tail尾部前一个结点
                removeNode(tail.pre);
                //数量--
                count--;
            }
            Node newNode = new Node(key, value);
            cache.put(key, newNode);
            //由于刚添加,把新数据结点移动到头部
            insertNode(newNode);
            count++;
        }
        //如果命中更新该key索引的node值,并移至开头
        else {
            node.value = value;
            //如果目前只有一个节点不用摘
            if(count == 1) {
                return;
            }
            moveToHead(node);

        }
    }

移除其中一个元素方法



    //移除缓存中的一个数据
    public void remove(int key) {
        Node node = cache.get(key);
        //没有就什么也不干,有就删除
        if (node == null)
            return;
        cache.remove(key);
        removeNode(node);
    }

    

LinkedHashMap实现LRU


package cn.work.demo.demo02;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

public class LRULinkedHashMapCache<K,V> extends LinkedHashMap<K,V> {
    //Cache容量
    private int size;
    //定义一个锁保证线程安全
    private final ReentrantLock lock = new ReentrantLock();

   //初始化,当参数accessOrder为true时,即会按照访问顺序排序,最近访问的放在尾部,最早访问的放在头部(数据由last添加至尾部)
    public LRULinkedHashMapCache(int size) {
        super(size,0.75f,true);
        this.size = size;
    }

    //重写removeEldestEntry方法,若当前容量>size,弹出尾部
    @Override
    public boolean removeEldestEntry(Map.Entry<K,V> eldest) {

        //size()方法,每当LinkedHashMap添加元素时就会++
        return size() > size;
    }

    //重写LinkedHashMap的方法,加锁保证线程安全
    @Override
    public V put(K key, V value) {
        try {
            lock.lock();
            return super.put(key, value);
        } finally {
            lock.unlock();
        }
    }
    @Override
    public V get(Object key) {
        try {
            lock.lock();
            return super.get(key);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public V remove(Object key) {
        try {
            lock.lock();
            return super.remove(key);
        } finally {
            lock.unlock();
        }
    }

}

完整代码


HashMap


package cn.work.demo.demo02;

import java.util.*;

//主要思路是:常命中(使用多)和刚添加的移至头部,缓存满了先淘汰尾部
//最近最久未使用 - 指的是Node里的数据,需要变换的也是Node,key只作为索引不考虑交换
//tail和head结点用于摘除结点,其中并不包含任何数据

public class LRUCache {

    //定义双列集合的结点结构(双向链表的结点)
    private class Node {
        //key的作用是cache满的时候,hashmap便于淘汰尾部和移除操作,还有遍历
        int key;
        int value;
        Node pre;
        Node post;

        //构造方法初始化Node数据
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }

        public Node() {

        }
    }

    //定义头尾结点
    private Node head;
    private Node tail;
    //定义当前缓存大小
    private int count;
    //定义总缓存大小
    private int size;
    //定义双列集合存储数据
    private HashMap<Integer, Node> cache;

    //构造方法初始化数据
    public LRUCache(int size) {
        //双向链表初始化
        head = new Node();
        tail = new Node();
        //结点外的指针置空
        head.pre = null;
        tail.post = null;
        //头尾结点的互连
        head.post = tail;
        tail.pre = head;

        //容量初始化
        this.count = 0;
        this.size = size;
        cache = new HashMap<>(size);
    }

    //get方法得到key中缓存的数据
    public int get(int key) {
        //取得hashmap中的结点数据
        Node node = cache.get(key);
        //如果没有返回-1
        if (node == null)
            return -1;

        //有,访问后将结点移动到开头,成为最近使用结点
        moveToHead(node);
        //并返回查询的值
        return node.value;
    }

    //摘除双链表结点
    private void removeNode(Node node) {
        node.pre.post = node.post;
        node.post.pre = node.pre;
    }

    //结点插入头部之后
    private void insertNode(Node node) {

        //前连插入结点
        node.pre = head;
        node.post = head.post;
        //后连插入结点
        head.post.pre = node;
        head.post = node;
    }

    private void moveToHead(Node node) {

        //摘除结点
        removeNode(node);
        //插入头结点之后
        insertNode(node);

    }

    //put方法存入数据,同时将值放入hashmap的node
    public void put(int key, int value) {
        //获取Node仓库
        Node node = cache.get(key);
        //如果没有命中就调进
        if (node == null) {
            //如果cache满了淘汰尾部
            if (count >= size) {
                cache.remove(tail.pre.key);
                //摘除tail尾部前一个结点
                removeNode(tail.pre);
                //数量--
                count--;
            }
            Node newNode = new Node(key, value);
            cache.put(key, newNode);
            //由于刚添加,把新数据结点移动到头部
            insertNode(newNode);
            count++;
        }
        //如果命中更新该key索引的node值,并移至开头
        else {
            node.value = value;
            //如果目前只有一个节点不用摘
            if (count == 1) {
                return;
            }
            moveToHead(node);

        }
    }

    //移除缓存中的一个数据
    public void remove(int key) {
        Node node = cache.get(key);
        //没有就什么也不干,有就删除
        if (node == null)
            return;
        cache.remove(key);
        removeNode(node);
    }

    //用于遍历cache
    public void print() {
        Set<Integer> keyset = cache.keySet();
        Iterator iterator = keyset.iterator();
        while (iterator.hasNext()) {
            int key = (int) iterator.next();
            System.out.println(cache.get(key).key + "-->" + cache.get(key).value);

        }

    }

}

LinkedHashMap

package cn.work.demo.demo02;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

public class LRULinkedHashMapCache<K,V> extends LinkedHashMap<K,V> {
    //Cache容量
    private int size;
    //定义一个锁保证线程安全
    private final ReentrantLock lock = new ReentrantLock();

    public LRULinkedHashMapCache(int size) {
        super(size,0.75f,true);
        this.size = size;
    }

    //初始化,当参数accessOrder为true时,即会按照访问顺序排序,最近访问的放在最前,最早访问的放在后面
    //重写removeEldestEntry方法,若当前容量>size,弹出尾部
    @Override
    public boolean removeEldestEntry(Map.Entry<K,V> eldest) {

        //size()方法,每当LinkedHashMap添加元素时就会++
        return size() > size;
    }

    //重写LinkedHashMap的方法,加锁保证线程安全
    @Override
    public V put(K key, V value) {
        try {
            lock.lock();
            return super.put(key, value);
        } finally {
            lock.unlock();
        }
    }
    @Override
    public V get(Object key) {
        try {
            lock.lock();
            return super.get(key);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public V remove(Object key) {
        try {
            lock.lock();
            return super.remove(key);
        } finally {
            lock.unlock();
        }
    }

}

主方法


package cn.work.demo.demo02;

import java.util.Map;

public class LRU {
    public static void main(String[] args) {
        LRUCache lru = new LRUCache(4);
        lru.put(1,2);
        lru.put(2,5);
        lru.put(8,10);
        lru.put(6,5);
        lru.get(1);
        lru.put(3,8);
        lru.get(8);
        lru.put(5,2);
        lru.put(6,2);
        lru.put(7,2);
        lru.print();
        System.out.println("//-------------------------------");
        LRULinkedHashMap<Integer,Integer> linkedHashMap= new LRULinkedHashMap<>(4);
        linkedHashMap.put(1,2);
        linkedHashMap.put(2,5);
        linkedHashMap.put(8,10);
        linkedHashMap.put(6,5);
        linkedHashMap.get(1);
        linkedHashMap.put(3,8);
        linkedHashMap.get(8);
        linkedHashMap.put(5,2);
        linkedHashMap.put(6,2);
        linkedHashMap.put(7,2);

        for (Map.Entry<Integer,Integer> entry:linkedHashMap.entrySet()){
            System.out.println(entry.getKey()+"-->"+entry.getValue());
        }

    }
}

结果



8-->10
5-->2
6-->2
7-->2
//-------------------------------
8-->10
5-->2
6-->2
7-->2

原文地址:https://www.cnblogs.com/huxiaobai/p/11708142.html

时间: 2024-11-12 08:29:27

LRU算法实现,HashMap与LinkedHashMap源码的部分总结的相关文章

LRU、FIFO缓存实现以及LinkedHashMap源码

本篇将描述如何使用LinkedHashMap实现LRU以及FIFO缓存,并将从LinkedHashMap源码层面描述是如何实现这两种缓存的. 1.缓存描述 首先介绍一下FIFO.LRU两种缓存: FIFO(First In First out):先见先出,淘汰最先近来的页面,新进来的页面最迟被淘汰,完全符合队列. LRU(Least recently used):最近最少使用,淘汰最近不使用的页面. 2.代码实现 以下是通过LinkedHashMap实现两种缓存. public class Ca

【Java集合源码剖析】LinkedHashmap源码剖析

LinkedHashMap简介 LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加入了一个双向链表的头结点,将所有put到LinkedHashmap的节点一一串成了一个双向循环链表,因此它保留了节点插入的顺序,可以使节点的输出顺序与输入顺序相同. LinkedHashMap可以用来实现LRU算法(这会在下面的源码中进行分析). LinkedHashMap同样是非线程安全的,只在单线程环境下使用. LinkedHashMap源码剖析 LinkedHashM

转:【Java集合源码剖析】LinkedHashmap源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/37867985   前言:有网友建议分析下LinkedHashMap的源码,于是花了一晚上时间研究了下,分享出此文(这个系列的最后一篇博文了),希望大家相互学习.LinkedHashMap的源码理解起来也不难(当然,要建立在对HashMap源码有较好理解的基础上). LinkedHashMap简介 LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加

【源码】LinkedHashMap源码剖析

注:以下源码基于jdk1.7.0_11 之前的两篇文章通过源码分析了两种常见的Map集合,HashMap和Hashtable.本文将继续介绍另一种Map集合--LinkedHashMap. 顾名思义,LinkedHashMap除了是一个HashMap之外,还带有LinkedList的特点,也就是说能够保持遍历的顺序和插入的顺序一致,那么它是怎么做到的呢?下面我们开始分析. 首先看构造器. public class LinkedHashMap<K,V> extends HashMap<K,

LinkedHashMap 源码分析

LinkedHashMap 源码分析 1. 基本结构 1. 实现 实现的接口是 Map 2. 继承 ?? 继承的是 HashMap 这个就比较熟悉了,事实上我们会看到 LinkedHashMap 代码量非常的少,主要就是因为他继承的 HashMap ,继承了大多数的操作. 仔细一点的都会发现 HashMap 里面有非常多的空白方法,这些方法其实是模板方法,为了让继承 HashMap 的类重写一些自己的特性.而不破坏代码结构. 3. 数据域 1. 基本字段 ?? 在 HashMap 的基础上他添加

深入Java基础(四)--哈希表(1)HashMap应用及源码详解

继续深入Java基础系列.今天是研究下哈希表,毕竟我们很多应用层的查找存储框架都是哈希作为它的根数据结构进行封装的嘛. 本系列: (1)深入Java基础(一)--基本数据类型及其包装类 (2)深入Java基础(二)--字符串家族 (3)深入Java基础(三)–集合(1)集合父类以及父接口源码及理解 (4)深入Java基础(三)–集合(2)ArrayList和其继承树源码解析以及其注意事项 文章结构:(1)哈希概述及HashMap应用:(2)HashMap源码分析:(3)再次总结关键点 一.哈希概

Java HashMap实现原理 源码剖析

HashMap是基于哈希表的Map接口实现,提供了所有可选的映射操作,并允许使用null值和null建,不同步且不保证映射顺序.下面记录一下研究HashMap实现原理. HashMap内部存储 在HashMap内部,通过维护一个 瞬时变量数组table (又称:桶) 来存储所有的键值对关系,桶 是个Entry对象数组,桶 的大小可以按需调整大小,长度必须是2的次幂.如下代码: /** * 一个空的entry数组,桶 的默认值 */ static final Entry<?,?>[] EMPTY

Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例

此页面为WP8"Surface Pro 3"应用的发布页面. "Surface Pro 3"是一款收集Surface Pro 3的玩机技巧的WP8程序,更好的帮助Surface用户理解并使用它. 此页面主要记录开发进度.APP发布等情况. -------------------相关进度--------------------- 目前进度:UI相关资源前期准备中,各相关开放平台的AppID申请中... Java 集合系列 09 HashMap详细介绍(源码解析)和使用

JDK1.8 HashMap中put源码分析

一.存储结构      在JDK1.8之前,HashMap采用桶+链表实现,本质就是采用数组+单向链表组合型的数据结构.它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置.HashMap通过key的hashCode来计算hash值,不同的hash值就存在数组中不同的位置,当多个元素的hash值相同时(所谓hash冲突),就采用链表将它们串联起来(链表解决冲突),放置在该hash值所对应的数组位置上.结构图如下:     图中,紫色部分代表哈希表,也称为哈希数组,数组中每个元素