linkedhashmap 和缓存

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

linkedhashmap继承了hashmap,

看看构造方法

 public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super(m);
        accessOrder = false;
    }

    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

构造方法中多了一个参数accessorder,剩下的两个都是hashmap中原先就有的,一个是装载因子,一个是最大容量。

看一下hashmap的构造方法

 public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

  
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);//默认容量是16,默认的加载因子是0.75
    }

    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        inflateTable(threshold);

        putAllForCreate(m);
    }

linkedhashmap中的那个accessorder的作用是什么,作用就是排序。可以看到默认情况下构造方法对这个参数的设置都是false,那么map中的数据的排序就是插入的顺序,如果改成true,那么排序的规则就是按照访问顺序。具体下面再说。

上面说到了装载因子,这个到底是干吗的,额外我多记录一下吧。

hashmap中添加元素的方法有两个putall和put,其中putall的实现最终还是调用了put方法,那么我们看看put

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        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;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
很明显put方法中最终调用的是addentry方法
  void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

可以看到在andentry的时候会有一个if检查,当map当前容量大于threshold就会执行resize,resize就是对hashmap进行扩容,可以看到每次扩容都是翻倍扩大。那么这个threshold是不是就是我们初始化时候的initialCapacity,答案是no。threshold是initialCapacity和装载因子 的乘积!ok,到现在都一目了然了。

回归正题,

上面说到addentry方法,linkedhashmap对这个方法进行了重写,但是并没有重写put。

看看重写之后的addentry

 void addEntry(int hash, K key, V value, int bucketIndex) {
        super.addEntry(hash, key, value, bucketIndex);

        // Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
    }
哈哈,不一样哈。看看if条件语句中的这个方法
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }
如果这个方法返回的是true,那么将会删除header.after这个节点,如果是按照插入顺序排序,那么就是最早插入的那个节点会被删除,如果是按照访问顺序排序,那么将删除,最久没有访问的节点(最近访问的节点会放在链表的最后)。但是这个方法默认是返回false,所以肯定不会执行这个删除操作。但是你看见没,访问限定权限是protect,也就是说如果你想改,你就继承linkedhashmap然后你去改,根据你自己想设置的条件,每次在插入map的时候都会检查,是不是满足你设置的条件,是的话那就删除最前面的节点,不是就继续留着咯。



说了这么多才是铺垫啊。。。

说缓存。大道理。。

计算机中为了解决cpu处理速度块,贮存存取速度慢的问题,所以设置了缓存,提高cpu的效率。硬件级别的缓存基本都是这个道理,缓存的添加提高了硬件设备的效率。

在软件中对于某些频繁使用的数据如果每次都到系统外部去获取是不是会很耗时耗力。。。因此也需要建立一个缓存,最简单的一个场景,我们现在经常需要数据库中的一些数据,先去缓存中看,没有再去查数据库,查到后直接用,并放进缓存以后使用。

在Java中最常见的一种实现缓存的方式就是使用Map,方便我们通过key标识缓存的内容。

缓存的机制有很多种,我们这里之说两种,一个是FIFO(先进先出),一个是LRU(最近最少使用)

至于先进先出,那就是队列啊,至于最近最少使用,刚才说到了linkedhashmap中的accessorder可以按照访问顺序排序,加上介绍的那个removeEldestEntry方法,有没有觉得,linkedhashmap专为缓存而生,简直一模一样啊。。。。啊哈啊

上代码把。。。

LinkedHashMap默认就是按存储的数据排序的,满足先进先出规则,所以可以继承LinkedHashMap实现基于FIFO的缓存

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

    private static final long serialVersionUID = 436014030358073695L;

    private final int SIZE;

    public FIFOCache(int size) {
        super();//调用父类无参构造,不启用LRU规则
        SIZE = size;
    }

    //重写淘汰机制
    @Override
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
        return size() > SIZE;  //如果缓存存储达到最大值删除最后一个
    }
}
public class CacheTest {

    public static void main(String[] args) {
        FIFOCache<Integer, Integer> map = new FIFOCache<Integer, Integer>(10);//设置容量为10
        for (int i = 0; i++ < 10;) {
            map.put(i, i);   //放入1-10总10个数据
        }
        System.out.println("起始存储情况:"+map.toString());//打印起始存储情况

        map.put(8, 8);  //存入一个已存在的数据,也就是命中一次缓存中的数据
        System.out.println("命中一个已存在的数据:"+map.toString());//打印命中之后的情况

        map.put(11, 11); //又存入缓存之外的数据
        System.out.println("新增一个数据后:"+map.toString());//打印又存储一个数据之后的情况
    }

}

//输出结果:
//起始存储情况:{1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 10=10}
//命中一个已存在的数据:{1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 10=10}
//新增一个数据后:{2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 10=10, 11=11}

从上面的测试结果来看,的确是FIFO规则的吧。首先是按存入的顺序进行排序的,如果命中缓存中的任意一个数据,也不会破坏先进先出的规则。如果新增了一个缓存之外的数据,会把最先存入的数据移除。

启用LinkedHashMap的LRU机制
public class LRUCache<K,V> extends LinkedHashMap<K, V> {
    private static final long serialVersionUID = 5853563362972200456L;

    private final int SIZE;

    public LRUCache(int size) {
        super(size, 0.75f, true);  //这里的true就是启用访问顺序的排序,不是按照插入顺序排序
        SIZE = size;
    }

    @Override
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
        return size() > SIZE;
    }
}
public class CacheTest {

    public static void main(String[] args) {
//      FIFOCache<Integer, Integer> map = new FIFOCache<Integer, Integer>(10);//设置容量为10

        LRUCache<Integer, Integer> map = new LRUCache<Integer, Integer>(10);

        for (int i = 0; i++ < 10; ) {
            map.put(i, i);   //放入1-10总10个数据
        }
        System.out.println("起始存储情况:"+map.toString());//打印起始存储情况

        map.get(7);
        System.out.println("命中一个已存在的数据:"+map.toString());//打印命中之后的情况

        map.put(8, 8+1);  //存入一个已存在的数据,也就是命中一次缓存中的数据
        System.out.println("覆盖一个已存在的数据:"+map.toString());//打印命中之后的情况

        map.put(11, 11); //又存入缓存之外的数据
        System.out.println("新增一个数据后:"+map.toString());//打印又存储一个数据之后的情况
    }

}

//输出结果
//起始存储情况:{1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 10=10}
//命中一个已存在的数据:{1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 8=8, 9=9, 10=10, 7=7}
//覆盖一个已存在的数据:{1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 9=9, 10=10, 7=7, 8=9}
//新增一个数据后:{2=2, 3=3, 4=4, 5=5, 6=6, 9=9, 10=10, 7=7, 8=9, 11=11}

从结果可以看到,每次访问一个那么就会把这个节点放到最后,标识为最近访问的,每次插入也是直接在最后,会把最前面的很久没访问的一个节点删除了。


ok ,就这么多把。。



猜你喜欢

转载自blog.csdn.net/u010365819/article/details/80894860