1.LinkedHashMap的内部结构
2.LinkedHashMap构造函数
3.元素新增策略
4.元素删除
5.元素修改和查找
6.特殊操做
7.扩容
8.总结java
对象的内部结构其实就是hashmap的内部结构,可是比hashmap的内部结构node要多维护2个引用指针,用来作前置和后置链表node
同事linkedhashmap自己还有头链表节点和尾部链表节点算法
static class Entry<K,V> extends MyHashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }
由于linkedhashmap是继承自hashmap,因此hashmap有的它都有,好比上面2的n次幂,扩容策略等等
那么就有意思了,linkedhashmap有什么独到的地方么???
既然是继承自hashmap,那么咱们看看hashmap没有的东西,或者被覆盖重写了的东西便可缓存
基本和hashmap一致,没法就是设置空间容量,负载因子等数据
这里空间容量和hashmap同样,也是取比当前容量大的最小2次幂ide
就说put吧,就是完彻底全调用的hashmap的 put方法。。。。晕
不过注意啊,再hashmap中有实打实大三个函数是为了linkedhashmap准备的,这个在源码中就说明了,而且put操做就用到了其中2个函数
这里能够吧以前hashmap中的这几个函数加上了解了this
还有个地方须要注意,linkedhashmap还重写了newnode方法,这个是为了和链表串联起来spa
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { //每当建立一个新的链表节点的时候,咱们调用linknodelast,吧当前添加到链表末尾 TestLinkedHashMap.Entry<K,V> p = new TestLinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); return p; } //不论这个节点是处于什么位置,都进行添加节点 private void linkNodeLast(TestLinkedHashMap.Entry<K,V> p) { TestLinkedHashMap.Entry<K,V> last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; } }
接下来咱们一一阐述hashmap专门为linkedhashmap预留的几个函数操作系统
/** * * @program: y2019.collection.map.TestLinkedHashMap * @description: 只有当put进去,这个值存放到hash桶上的时候,而且这个值是以前存在的,(或者是树状结构),才会触发这个函数 * @auther: xiaof * @date: 2019/8/29 17:03 */ void afterNodeAccess(Node<K,V> e) { // move node to last TestLinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { //获取这个节点的前置,和后置引用对象 TestLinkedHashMap.Entry<K,V> p = (TestLinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; //把后置设置为空 p.after = null; //若是替换的对象没有前置节点,那么就把当前节点当作head if (b == null) head = a; else b.after = a; //不然创建双向链表数据,前置改成a //吧a的前置改为b if (a != null) a.before = b; else last = b; //而后吧tail指向p,这样就把p从原来的链表中,断裂开,而后拼接到tail后 if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; //容器类修改次数++ ++modCount; } }
这个是linkedhashmap再实现lrucache的时候会调用到的方法,平时没有做用.net
根据evict 和 判断是否须要删除最老插入的节点,后面咱们实现lrucache的时候再详细了解
Linkedhashmap的删除操做和hashmap一致,可是还有一个函数被重写了,就是这里有点不同
其实操做就是,linkedhashmap由于是一个双向链表,因此在删除的时候就是作一个对双向链表进行删除的操做
这个方法就是
AfterNodeRemoval 把从hashmap中删除的元素,断开双向链表的链接
//把从hashmap中删除的元素,断开双向链表的链接 void afterNodeRemoval(Node<K, V> e) { // unlink TestLinkedHashMap.Entry<K, V> p = (TestLinkedHashMap.Entry<K, V>) e, b = p.before, a = p.after; p.before = p.after = null; if (b == null) head = a; else b.after = a; if (a == null) tail = b; else a.before = b; }
对于查找get元素,这里linkedhashmap就直接重写了,可是里面调用getnode其实仍是hashmap那一套,不过就是多了个判断accessOrder参数,若是为true就会调用afterNodeAccess
这个方法前面有讲到
由于是链表的缘故,因此这里是直接循环遍历链表一次便可
public boolean containsValue(Object value) { for (TestLinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) { V v = e.value; if (v == value || (value != null && value.equals(v))) return true; } return false; }
而hashmap呢?
public boolean containsValue(Object value) { Node<K,V>[] tab; V v; if ((tab = table) != null && size > 0) { //先循环hash桶 for (int i = 0; i < tab.length; ++i) { //而后遍历链表 for (Node<K,V> e = tab[i]; e != null; e = e.next) { if ((v = e.value) == value || (value != null && value.equals(v))) return true; } } } return false; }
要实现lru首先要明白这是个什么?
近期最少使用算法。 内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫作LRU,操做系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。
为何用linkedhashmap呢?由于这个容器事实是一个双向链表,并且里面带上参数的构造函数的时候,前面用的get方法会调用到afterNodeAccess方法,这个方法会吧最近get的数据从新指引向链表末尾
基于这点咱们只要吧accessOrder设置为true便可
package y2019.collection.map; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; /** * @ProjectName: cutter-point * @Package: y2019.collection.map * @ClassName: TestLRUCache * @Author: xiaof * @Description: 实现lru (最近最不常使用)缓存 * 获取数据(get)和写入数据(set)。 * 获取数据get(key):若是缓存中存在key,则获取其数据值(一般是正数),不然返回-1。 * 写入数据set(key, value):若是key尚未在缓存中,则写入其数据值。 * 当缓存达到上限,它应该在写入新数据以前删除最近最少使用的数据用来腾出空闲位置。 * @Date: 2019/9/3 16:42 * @Version: 1.0 */ public class TestLRUCache<K, V> { LinkedHashMap<K, V> cache = null; int cacheSize; public TestLRUCache(int cacheSize) { //默认负载因子取0.75 this.cacheSize = (int) Math.ceil(cacheSize / 0.75f) + 1;//向上取整数 cache = new LinkedHashMap<K, V>(this.cacheSize, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { //这里有个关键的负载操做,由于是lru,因此当长度超了的时候,不是扩容,而是吧链表头干掉 System.out.println("size=" + this.size()); return this.size() > cacheSize; } }; } public V get(K key) { return cache.get(key); } public V set(K key, V value) { return cache.put(key, value); } public void setCacheSize(int cacheSize) { this.cacheSize = cacheSize; } public void printCache(){ for(Iterator it = cache.entrySet().iterator(); it.hasNext();){ Map.Entry<K,V> entry = (Map.Entry<K, V>)it.next(); if(!"".equals(entry.getValue())){ System.out.println(entry.getKey() + "\t" + entry.getValue()); } } System.out.println("------"); } public void PrintlnCache(){ Set<Map.Entry<K,V>> set = cache.entrySet(); for(Map.Entry<K,V> entry : set){ K key = entry.getKey(); V value = entry.getValue(); System.out.println("key:"+key+"value:"+value); } } public static void main(String[] args) { TestLRUCache<String,Integer> lrucache = new TestLRUCache<String,Integer>(3); lrucache.set("aaa", 1); lrucache.printCache(); lrucache.set("bbb", 2); lrucache.printCache(); lrucache.set("ccc", 3); lrucache.printCache(); lrucache.set("ddd", 4); lrucache.printCache(); lrucache.set("eee", 5); lrucache.printCache(); System.out.println("这是访问了ddd后的结果"); lrucache.get("ddd"); lrucache.printCache(); lrucache.set("fff", 6); lrucache.printCache(); lrucache.set("aaa", 7); lrucache.printCache(); } }
参考hashmap
借助linkedhashmap实现lru,重点就是再大小范围超出的时候进行删除头结点,而不是扩容
参考:https://blog.csdn.net/zxt0601/article/details/77429150https://www.jianshu.com/p/d76a78086c3a