zoukankan      html  css  js  c++  java
  • java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现

    java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现

    实验

    遍历HashMap

    public static void main(String[] args)
    	{
    		Map<String, String> map=new HashMap<String,String>();
    	    map.put("white", "小白");
    	    map.put("black", "小黑");
    	    map.put("red", "小红");
    	    map.put("yellow", "小黄");
    	    map.get("yellow");
    	    map.get("red");
    	    map.get("black");
    	    map.get("white");
    	    
    	    Iterator iter = map.entrySet().iterator();
    	    while (iter.hasNext()) {
    	        Map.Entry entry = (Map.Entry) iter.next();
    	        System.out.println(entry.getKey() + "=" + entry.getValue());
    	    }
    }
    
    • 结果发现,hashmap遍历出来的是无序的

    换成LinkedHashMap

    	Map<String, String> map=new LinkedHashMap<String,String>();
    
    • 结果发现,遍历出来的顺序是按找插入的顺序

    改变LinkedHashmap的参数

    Map<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true);
    
    • 结果发现,遍历出来的顺序是按照访问的顺序的来的

    分析

    与HashMap的关系

    • LinkedHashMap可以看做是LinkedList+hashmap的组合
    • LinkedHashMap是hashmap的子类
    public class LinkedHashMap<K,V>
        extends HashMap<K,V>
        implements Map<K,V>
    
    • LinkedHashMap中的Entry有before和after两个域,这是用来实现双向链表的
     private static class Entry<K,V> extends HashMap.Entry<K,V> {
            // These fields comprise the doubly linked list used for iteration.
            Entry<K,V> before, after;
    
            Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
                super(hash, key, value, next);
            }
    
    

    构造方法

    LinkedHashMap
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder)构造一个带指定初始容量、加载因子和排序模式的空 LinkedHashMap 实例。 
    
    参数:
    initialCapacity - 初始容量
    loadFactor - 加载因子
    accessOrder - 排序模式 - 对于访问顺序,为 true;对于插入顺序,则为 false 
    抛出: 
    IllegalArgumentException - 如果初始容量为负或者加载因子为非正
    
    
    • 从api可以看出,有一个参数accessOrder,默认情况下该参数为false,为false的时候,顺序为插入顺序,如果为true的话,顺序为访问顺序
    public LinkedHashMap() {
          super();
          accessOrder = false;
      }
      void init() {
          header = new Entry<K,V>(-1, null, null, null);
          header.before = header.after = header;
      }
    
    • 从这里看出,还未put的时候就创建了一个header

    put方法

    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        //如果table数组该下标的内容不为空,遍历链表
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
    //如果没有该key
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
    
    • put 方法继承自hashmap的,不同的是addEntry被重写了,如果put的键值对中是新的,那么执行addEntry方法

    addEntry方法

    void addEntry(int hash, K key, V value, int bucketIndex) {
        createEntry(hash, key, value, bucketIndex);
    
        // Remove eldest entry if instructed, else grow capacity if appropriate
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        } else {
            if (size >= threshold)
                resize(2 * table.length);
        }
    }
    
    • addEntry方法先调用createEntry方法

    createEntry方法

    
    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++;
    }
    
    • 这里将Entry放到table之后,还执行了一个addBefore方法

    addBefore方法

    private void addBefore(Entry<K,V> existingEntry) {
        after  = existingEntry;
        before = existingEntry.before;
        before.after = this;
        after.before = this;
    }
    
    • 这个方法用于维护双向链表,可以自己画一个图看一下

    回到addEntry方法,执行完createEntry方法后,下面的部分代码

     // Remove eldest entry if instructed, else grow capacity if appropriate
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        } else {
            if (size >= threshold)
                resize(2 * table.length);
        }
    
    • 第一句注释的意思是如果指示那么删除最年长的,否则扩容
    • 如果removeEldestEntry返回true,那么删除老的键值对,否则的话如果超过阈值,进行扩容

    removeEldestEnrey方法

    protected boolean More removeEldestEntry(Map.Entry<K,V> eldest) {
            return false;
        }
    
    • removeEldestEntry方法,也就是是否删除最年长的Entry。这里始终返回的是false,我们可以对他进行重写

    recordAccess方法

    • 当put的是新的键值对,键是之前没有的话,那么会创建一个新的Entry,而如果是之前有的话,那么会覆盖value并执行revordAccess,中文意思为记录访问
    void recordAccess(HashMap<K,V> m) {
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
        if (lm.accessOrder) {
            lm.modCount++;
            remove();
            addBefore(lm.header);
        }
    }
    private void remove() {
        before.after = after;
        after.before = before;
    }
    private void addBefore(Entry<K,V> existingEntry) {
        after  = existingEntry;
        before = existingEntry.before;
        before.after = this;
        after.before = this;
    }
    
    • 这个方法会调用remove方法,而这个方法实际上是改变链表的指针,执行后,相当于之前没有这个这个Entry(可以自己动手画一下指针指向),然后再将这个Entry重新加入,这个时候以前添加的Entry,由于最近被使用,就被移到到链表的后面了
    • 同样使用get的时候也会调用此方法,也就是会由于被访问被移动到前面

    总结

    • 初始化一个LinkedHash,会创建一个header
    • put的时候,将Entry放入数组中后,会和header连在一起
    • 再put的一个的时候,同样加入数组后,会继续和前面一个相连,此时header,小黑,小红三个连在一起,
    • 此时get小黑,小黑会从链表中移除,此时相当于只添加了小红,然后再将小黑添加进去,这样的话就保证了,最新访问的在后面
    • 如果put的时候该键是之前已有的,那么会覆盖value,然后重新调整该Entry在链表中的位置

    LRU实现

    • 所谓LRU也就是Least Recently Used,最近最少使用,当缓存满了,删除最少使用的
    • 那么LinkedHashMap适合于解决这个问题,因为他使用了双向链表,将最新访问的移到了后面,那么这样的话,前面的就是最少使用,这时候就可以将最少使用的删除
    • removeEldestEntry,前面说过这方法,这个方法就是删除最少使用的,默认下不会删除,因为该方法返回false,所以当空间满了的话会扩容,我们可以重写这个方法,那么这样就不需要扩容,满了的时候删除最少使用的就可以
    • 重写removeEldestEntry,当大小大于容量的时候,会删除最年长的键值对
    public class LRUCache {
        private int capacity;
        private Map<Integer, Integer> cache;
        public LRUCache(int capacity) {
            this.capacity = capacity;
            this.cache = new LinkedHashMap<Integer, Integer> (capacity, 0.75f, true) {
                // 定义put后的移除规则,大于容量就删除eldest
                protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
                    return size() > capacity;
                }
            };
        }
        public int get(int key) {
            if (cache.containsKey(key)) {
                return cache.get(key);
            } else
                return -1;
        }
        public void set(int key, int value) {
            cache.put(key, value);
        }
        public static void main(String[] args) {
    		LRUCache cache=new LRUCache(2);
    		cache.set(1,1);
    		cache.set(2, 2);
    		//此时超出容量,put3的时候删除cache
    		cache.set(3, 3);
    		Set<Map.Entry<Integer,Integer>> set = cache.cache.entrySet();
    	    Iterator<Map.Entry<Integer,Integer>> iterator = set.iterator();
    	    
    	    while (iterator.hasNext())
    	    {
    	        System.out.print(iterator.next() + "	");
    	    }
    	    System.out.println();
    		
    		//输出2=2	3=3
    		
    	}
    }
    

    我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)

    作者:jiajun 出处: http://www.cnblogs.com/-new/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

  • 相关阅读:
    Leecode刷题之旅-C语言/python-67二进制求和
    maven 聚合
    maven 继承
    maven 常用命令
    maven 术语
    maven安装
    RabbitMQ 消费消息
    RabbitMQ 生产消息并放入队列
    RabbitMQ 在 web 页面 创建 exchange, queue, routing key
    mybatis 通过实体类进行查询
  • 原文地址:https://www.cnblogs.com/-new/p/7484680.html
Copyright © 2011-2022 走看看