用LindedHashMap实现LRU算法

  LRU,最近最少使用。如果我们用一个数据结构来实现LRU的话,那么需要满足两个条件,第一个该数据结构需要存储最近使用或未使用的,第二个,需要限制这个数据结构的大小。

  我们用LindedHashMap实现LRU,第一需要设置accessOrder,在默认情况下,accessOrder是为false,表示顺序为插入顺序。为true时,表示会根据访问顺序排序(在get时),最新使用的排在尾巴上。初始化设置accessOrder的源码如下:

    /**
     * Constructs an empty <tt>LinkedHashMap</tt> instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - <tt>true</tt> for
     *         access-order, <tt>false</tt> for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

  在get或者方法里都会调用afterNodeAccess,这个方法就是把当前node放在尾巴上(看不明白没关系,看到这个注释了吗,// move node to last,hhhhhh)

    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            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的顺序是访问顺序,最新使用的在后面。然后我们需要重写removeEldestEntry这个方法,这个方面默认是返回false,表示不需要移除第一个,需要重写它,表示需要移除第一个,即最近未使用的。在源码里也有说明:

/**
     *.........
     * <p>Sample use: this override will allow the map to grow up to 100
     * entries and then delete the eldest entry each time a new entry is
     * added, maintaining a steady state of 100 entries.
     * <pre>
     *     private static final int MAX_ENTRIES = 100;
     *
     *     protected boolean removeEldestEntry(Map.Entry eldest) {
     *        return size() &gt; MAX_ENTRIES;
     *     }
     * </pre>
     *
     *...........
     */
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

  所以我们只需要这样重写,当Map的最大值大于设定的最大值时,需要移除第一个:

  LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer,Integer>(maxSize, 0.75f, true){
            @Override
            protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest) {
                return this.size() > maxSize;
            }
        };

  这样重写就好啦,当我们每次使用LinkedHashMap里的元素时,使用的这个元素就会放在最后面,最长未使用的就会在前面。当新加的元素超过Map的大小时,就会移除第一个,然后把新加的放在最后面。

附上测试代码和结果;

/**
 * main
 *
 * @description 测试
 * @author zhui
 * @date 2020-12-23 14:09
 * @version v1.0.0
 */
public class main {
    private static ExecutorService executorService;
    public static void main(String[] args) throws InterruptedException{
        createThreadPool();
        LRUByLinkedHashMap(3);
    }

    /**
     * @description 创建一个单线程,用来测试
     * @return void
     **/
    public static void createThreadPool(){
        executorService = Executors.newSingleThreadExecutor();
    }

    /**
     * @description 测试代码
     * @return void
     **/
    public static void LRUByLinkedHashMap(int maxSize) throws InterruptedException{
        // LRU的LinkedHashMap
        LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer,Integer>(maxSize, 0.75f, true){
            @Override
            protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest) {
                return this.size() > maxSize;
            }
        };
        for (int i = 1; i <= maxSize; i++) {
            map.put(i, 0);
        }
        System.out.println("初始map:" +map);

        // 测试
        for (int i = 1; i <= 10; i++){
            int temp = randInt(1,100);
            if (temp % 2 == 0) {
                // 对map的key随机get
                randomGetValue(maxSize, map);
            }else {
                // 对map进行随机put
                randomPutValue(maxSize,map);
            }
        }
        // 关闭单线程
        executorService.shutdown();
    }
    /**
     * @description 对map随机取值,观察其顺序变化
     * @param maxSize map大小,作为key取值范围
     * @param map map
     **/
    public static void randomGetValue(int maxSize, Map<Integer, Integer> map) throws InterruptedException{
        final CountDownLatch end = new CountDownLatch(maxSize);
        for(int i=1; i<= maxSize; i++){
            executorService.execute(() -> {
                int key =  randInt(1, maxSize);
                forGetValueByRandom(key, map);
                System.out.println("key=" + key + ",随机取值后map:" + map);
                end.countDown();
            });
        }
        end.await();
    }

    /**
     * @description 给map新put 1个键值对,观察剩下的map变化
     * @param maxSize map的size大小,决定新键值对的下限
     * @param map map
     * @return void
     **/
    public static void randomPutValue(int maxSize, Map<Integer, Integer> map){
        executorService.execute(() -> {
            int key = randInt(maxSize+1, 10);
            forGetValueByRandom(key, map);
            System.out.println("插入" + key + "后,map:" + map);
        });
    }

    /**
     * @description 根据key,随机对这个key进行get1次,没有则添加一个键值对,同时更新其value
     * @param key key
     * @param map map
     * @return void
     **/
    public static void forGetValueByRandom(int key, Map<Integer, Integer> map){
        Integer num = map.getOrDefault(key, 0);
        map.put(key, num + 1);
    }

    /**
     * @description 在一个范围内随机取值
     * @param min 随机取值最小值
     * @param max 随机取值最大值
     * @return int
     **/
    public static int randInt(int min, int max) {
        Random rand = new Random();
        int randomNum = rand.nextInt((max - min) + 1) + min;
        return randomNum;
    }
}

  然后我们看些结果,是否和预期一样呢?可以看到新加的元素或者刚使用的元素,都会在最后面,简直完美!

初始map:{1=0, 2=0, 3=0}
插入9后,map:{2=0, 3=0, 9=1}            //1被移除了
key=3,随机取值后map:{2=0, 9=1, 3=1}     //3放到最后面
key=1,随机取值后map:{9=1, 3=1, 1=1}     //2被移除,1在最后面
key=3,随机取值后map:{9=1, 1=1, 3=2}     //3在最后面
插入8后,map:{1=1, 3=2, 8=1}            //8在最后面
插入7后,map:{3=2, 8=1, 7=1}            //7在最后面
key=3,随机取值后map:{8=1, 7=1, 3=3}     //3在最后面
key=3,随机取值后map:{8=1, 7=1, 3=4}     //3在最后面
key=2,随机取值后map:{7=1, 3=4, 2=1}     //2在最后面
key=1,随机取值后map:{3=4, 2=1, 1=1}     //1在最后面
key=1,随机取值后map:{3=4, 2=1, 1=2}     //1在最后面
key=2,随机取值后map:{3=4, 1=2, 2=2}     //2在最后面
key=2,随机取值后map:{3=4, 1=2, 2=3}     //2在最后面
key=1,随机取值后map:{3=4, 2=3, 1=3}     //1在最后面    
key=1,随机取值后map:{3=4, 2=3, 1=4}     //1在最后面
插入8后,map:{2=3, 1=4, 8=1}            //8在最后面
插入4后,map:{1=4, 8=1, 4=1}            //4在最后面
插入6后,map:{8=1, 4=1, 6=1}            //6在最后面
posted @ 2020-12-23 17:17  real_zhui  阅读(128)  评论(0编辑  收藏  举报