LRU algorithm, summarizes HashMap and LinkedHashMap source

Some conclusions on HashMap and LinkedHashMap source

  • When the underlying structure after JDK1.8 HashMap, the array (Node <K, V> table) and a length greater than 64 when the chain (still Node) of length greater than 8, when the list is converted to red-black tree, chain length 6 or less will not be converted to red-black tree. The purpose is to ensure efficiency. Wherein the linked list node only next, LinkedHashMap in Entry <K, V> added before, after (defined doubly linked list), the iteration can be guaranteed, when traversed sequentially deposited.

  • The following is a doubly linked list of definitions 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;
        }
    }
  • There is a Boolean value LinkedHashMap accessOrder can change the order of access (get), default is false, does not change, put the true key to access into the tail, it is the key to LRU, and this value can be obtained in the constructor.

  • 0.75f ​​the default load factor, if the user-defined size larger than capacity * 0.75 (volume of threshold), the array is for expansion, load should not be too small, too easy hash collision, a waste of space is too small

  • About removeEldestEntry Method: Default returns false, it performs the following is true if the source node removal head

    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);
        }
    }
  • But the two are not thread-safe plus no synchronization lock (synchronized), and HashTable is safe

HashMap achieve LRU


  • The main idea is: often hit, using multiple, and just added to move the head, the tail out of the first buffer is full
  • Least recently use - This refers to Node, also we need to change the Node, key just as the index does not consider exchange
  • for head and tail removed junction node, which does not contain any data

Node Initialization


//定义双列集合的结点结构(双向链表的结点)
    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() {

        }
    }

Member variables defined cache


    //定义头尾结点
    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);
    }

Access cached content method


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

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

After the current node moves before the head node


    //摘除双链表结点
    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);

    }

Tune into the method


//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);

        }
    }

The method of removing one of the elements



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


    

LinkedHashMap achieve 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();
        }
    }


}

The complete code


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();
        }
    }


}

Main method


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());
        }

    }
}

result


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

Guess you like

Origin www.cnblogs.com/huxiaobai/p/11708142.html