Java中的LinkedHashMap及LRU緩存機制詳解
LinkedHashMap
- 維持插入順序;如 (1,“a”) (2, “b”)(先插的先訪問)
- 維持訪問順序(將最近訪問的數(shù)據(jù)移到鏈表的尾部 LRU思想 afterNodeAccess(里面處理了Entry的before after屬性))
- 主要是底層維護了一個雙向鏈表
- 不能被克隆和序列化(HashMap可以)
- LinkedHashMap的put類似于HashMap的 ; LinkedKeyIterator里調(diào)用nextNode() 源碼716行 modcount fast-fail
實現(xiàn)LRU緩存機制
- Least Recently Used的縮寫,即最近最少使用
- LRU緩存機制類似LinkedHashMap的雙向鏈表
- LRU緩存:內(nèi)存訪問時,設計一個緩存(如內(nèi)存4G,其中1G設置為緩存),大小固定,讀取內(nèi)存數(shù)據(jù),首先會去找緩存是否命中
- 如果命中,直接返回
- 反之,未命中從內(nèi)存中讀取數(shù)據(jù),把數(shù)據(jù)繼續(xù)添加到緩存當中,
- 如果緩存已滿,刪除訪問時間最早的數(shù)據(jù)
代碼測試驗證看下面的LRU代碼。
1)使用LinkedHashMap實現(xiàn)LRU緩存
需要重寫removeEldestEntry方法,該方法:
a. 通過 返回結(jié)果 去刪除訪問 時間最早 的數(shù)據(jù)
b. map的size()與給定緩存的最大size比較,如果map.size > MaxSize,則return true
c. 參數(shù)Map.Entry<K,V> eldest
package collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; class LRUCache<K,V> extends LinkedHashMap<K,V>{ private int maxSize; //緩存的大小 //重寫 public LRUCache(int maxSize){ super(16, 0.75f, true); this.maxSize = maxSize; } //protected @Override protected boolean removeEldestEntry(Map.Entry<K,V> eldest){ return size() > maxSize;//根據(jù)大小,決定是否刪除最早結(jié)點 } } public class Teacher_1_15_LinkedHashMap { public static void main(String[] args) { //使用LinkedHashMap實現(xiàn)LRU緩存 //大小為3 LRUCache<String, String> cache = new LRUCache<>(3); cache.put("a", "jfdshjhf"); //a cache.put("b", "fdjhfjf"); //a - > b cache.put("c", "all"); //a -> b -> c cache.get("a"); //b -> c -> a cache.put("d", "tulun"); //c -> a -> d System.out.println(cache); //維護插入順序 // LinkedHashMap<Integer, String> map = new LinkedHashMap<>(); // map.put(1, "ajhd"); // map.put(5, "fsd"); // map.put(12, "ref"); // map.put(10, "gdfs"); // map.put(19, "hgf"); // map.put(21, "hgf"); // // Iterator<Integer> iterator = map.keySet().iterator(); // while(iterator.hasNext()){ // System.out.println(iterator.next()); // } // //維持訪問順序 加載因子:f類型 accessOrder // LinkedHashMap<Integer, String> map = new LinkedHashMap<Integer, String>(16, 0.75f, true); // map.put(1, "ajhd"); // map.put(5, "fsd"); // map.put(12, "ref"); // map.put(10, "gdfs"); // map.put(19, "hgf"); // map.put(21, "hgf"); // // map.get(12); // map.get(10); // // Iterator<Integer> iterator = map.keySet().iterator(); // while(iterator.hasNext()){ // System.out.println(iterator.next()); // } } }
2)簡單的自定義實現(xiàn)(筆試)
設置頭結(jié)點 尾節(jié)點
存數(shù)據(jù)的容器:HashMap
維護雙向鏈表,確保 數(shù)據(jù)按照訪問時間存儲
package collection; import java.util.HashMap; import javax.swing.text.html.HTMLDocument.Iterator; class MyLRUCache<K,V>{ private Node<K,V> head = new Node<>();//頭結(jié)點 無參構(gòu)造 private Node<K,V> tail = new Node<>();//尾 private final HashMap<Integer,Node> hashMap = new HashMap<>();//new了,作為容器 private int size; //實際大小 private int capacity;//最大容量 class Node<K, V>{ private K key; private V value; private Node pre;//雙向鏈表 private Node next; //構(gòu)造器 public Node(){ } public Node(K key, V value){ this.key = key; this.value = value; } } //初始化 public MyLRUCache(int capacity){ head.next = tail;//先指向無效的結(jié)點,專門指向 頭和尾 tail.pre = head; this.capacity = capacity; size = 0; } //添加某個節(jié)點(尾插法) private void add(Node node){ //put里已判斷key是否存在,這里只管添加 //空表 if(tail.pre==head) { head.next=node;//由head指向node //node為首結(jié)點,前面再無,故不用設置node.pre tail.pre=node; }else { //Tail之后添加 node.pre=tail.pre; tail.pre.next=node; tail.pre=node; } } //刪除某個節(jié)點 private void remove(Node node){ //非空表 if(head.next!=tail) {//比地址 //頭結(jié)點 if(node==head.next) { head.next=head.next.next; //新頭結(jié)點 //head.next.pre=null; //新頭結(jié)點不需要設置pre }else if(node==tail.pre) { //尾結(jié)點 tail.pre=tail.pre.pre;//新尾結(jié)點 //tail.pre.pre.next=null; }else { //其他 node.pre.next=node.next;//要刪除結(jié)點的前面結(jié)點的next指針往后指一個 node.next.pre=node.pre;//要刪除結(jié)點的后面結(jié)點的 pre指針往前指一個 } } } //添加key-value鍵值對 public void put(K key, V value){ Node node = hashMap.get(key);//hashMap里已存有相同key的結(jié)點 if(node != null){ node.value = value; //從鏈表中刪除node節(jié)點 remove(node); //再將node節(jié)點尾插法重新插入鏈表(因為它剛被訪問) add(node); }else{ if(size < capacity){ size++; }else{ //超容量(1個),刪除緩存中最近最少使用的節(jié)點head.next //注意先從HashMap里刪除,不然remove會改變head.next值 hashMap.remove(head.next.key);//刪除指定key的結(jié)點 remove(head.next);//head.next變?yōu)橹赶騢ead.next.next了 } //將newNode尾插法插入鏈表 Node newNode = new Node(key, value);//將輸入的key value包裝成node hashMap.put((Integer) key, newNode);//存到hashMap,鍵為隨機值,值為node結(jié)點 (int)(Math.random()*100) add(newNode); } } //獲取value(返回8,表示成功)(將訪問的元素移到鏈表尾) public int get(K key){ Node node = hashMap.get(key); if(node == null){ return -1; } //刪除node hashMap.remove(node.key);//刪除指定key的結(jié)點 remove(node); //尾插法重新插入 add(node); hashMap.put((Integer) key, node); //等價于個數(shù)沒變 return 8; } public void show() { Node<K,V> temp = head; while(temp!=tail.pre){//tail.pre指向的是末尾節(jié)點 temp=temp.next; System.out.print(temp.key+" "); } System.out.println(); } } public class Teacher_1_15_MyLRUCache { public static void main(String[] args) { //維護插入順序 MyLRUCache<Integer, String> cache = new MyLRUCache<>(3); cache.put(1, "ajhd"); cache.put(5, "fsd"); cache.put(12, "ref"); cache.show();//應該1 5 12 System.out.println(cache.get(5));//成功則返回8 cache.show(); //應該1 12 5 cache.put(10, "gdfs"); cache.show();//應該12 5 10 //如何打印雙向鏈表里的順序 System.out.println(cache.get(1)); } }
到此這篇關于Java中的LinkedHashMap及LRU緩存機制詳解的文章就介紹到這了,更多相關LinkedHashMap及LRU緩存機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java修飾符abstract與static及final的精華總結(jié)
abstract、static、final三個修飾符是經(jīng)常會使用的,對他們的概念必須非常清楚,弄混了會產(chǎn)生些完全可以避免的錯誤,比如final和abstract不能一同出現(xiàn),static和abstract不能一同出現(xiàn),下面我們來詳細了解2022-04-04java中Base64字符串出現(xiàn)不合法字符的問題解決
非法的base64數(shù)據(jù)可能導致編碼或解碼過程出錯,本文主要介紹了java中Base64字符串出現(xiàn)不合法字符的問題解決,具有一定的參考價值,感興趣的可以了解一下2024-06-06javax.validation包里@NotNull等注解的使用方式
這篇文章主要介紹了javax.validation包里@NotNull等注解的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01SpringBoot使用@Cacheable注解實現(xiàn)緩存功能流程詳解
最近一直再學Spring Boot,在學習的過程中也有過很多疑問。為了解答自己的疑惑,也在網(wǎng)上查了一些資料,以下是對@Cacheable注解的一些理解2023-01-01springBoot項目集成quartz開發(fā)定時任務案例及注意事項
這篇文章主要介紹了springBoot項目集成quartz開發(fā)定時任務案例及注意事項,這些功能的主要接口(API)是Scheduler接口。它提供了簡單的操作,例如:將任務納入日程或者從日程中取消,開始/停止/暫停日程進度,需要的朋友可以參考下2022-06-06SpringCloud項目中Feign組件添加請求頭所遇到的坑及解決
這篇文章主要介紹了SpringCloud項目中Feign組件添加請求頭所遇到的坑及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04Java成員變量與局部變量(動力節(jié)點Java學院整理)
這篇文章主要介紹了Java成員變量與局部變量的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-04-04