电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

Java 手寫LRU緩存淘汰算法

瀏覽:2日期:2022-08-11 16:11:59
目錄概述LRU 的原理LRU 算法的實現LRU 算法描述LRU 算法代碼實現方法一方法二方法三總結概述

LRU 算法全稱為 Least Recently Used 是一種常見的頁面緩存淘汰算法,當緩存空間達到達到預設空間的情況下會刪除那些最久沒有被使用的數據 。

常見的頁面緩存淘汰算法主要有一下幾種:

LRU 最近最久未使用 FIFO 先進先出置換算法 類似隊列 OPT 最佳置換算法 (理想中存在的) NRU Clock 置換算法 LFU 最少使用置換算法 PBA 頁面緩沖算法 LRU 的原理

LRU 算法的設計原理其實就是計算機的 局部性原理(這個 局部性原理 包含了 空間局部性 和 時間局部性 兩種策略)。LRU 算法主要是依據 時間局部性策略 來設計的。

這個策略簡單來說就是,如果一個數據被訪問了,那么在短時間內它還會被訪問。

Java 手寫LRU緩存淘汰算法

同樣的,針對一個緩存數據,如果其使用的時間越近,那么它被再次使用的概率就越大,反之一個緩存數據如果很長時間未被使用,那它會被再次使用的概率就會很小。因而當緩存空間不足時,我們優先刪除最久未被使用的緩存數據,進而提高緩存命中率。

LRU 算法的實現LRU 算法描述

緩存在使用時,核心 API 有兩個:

int get(int key) 如果關鍵字 key 存在于緩存中,則返回關鍵字的值,否則返回 -1 。 void put(int key, int value) 如果關鍵字已經存在,則變更其數據值;如果關鍵字不存在,則插入該組「關鍵字-值」。當緩存容量達到上限時,它應該在寫入新數據之前刪除最久未使用的數據值,從而為新的數據值留出空間。

具體使用的例子如下:

//初始化一個緩存,并將緩存空間設置為2LRUCache cache = new LRUCache(2);cache.put(1,1); // cache = [(1,1)]cache.put(2,2); // cache = [(2,2),(1,1)]cache.get(1); //返回1cache.put(3,3) //cache = [(3,3),(2,2)],緩存空間已滿,需要刪除空間騰出位置,因而刪除最久未被使用的(1,1)cache.get(1); //返回 -1 因為(1,1)已經被刪除,因而返回 -1LRU 算法代碼實現

分析上面的算法操作,如果想要讓 put 和 get 方法的時間復雜度位 O(1),cache 的數據結構應該具有如下特點:

cache 中的元素必須是具有時序的,這樣才能區分最近使用的和最久未使用的數據 在 cache 中能夠快速的通過 key 來找到對應的 val。 每次訪問 cache 的某個 key 時需要將這個元素變成最近使用的,也就是說 cache 要支持在任意位置快速插入和刪除元素。

那么有什么數據結構同時符合上邊所有的要求那?HashMap 可以根據某個 key 快速定位到對應的 val,但是它不具有時序性(存儲的數據沒有順序)。LinkedList 似乎支持快速插入和刪除元素,而且具有固定順序,但它并不支持快速查找。所以我們可以考慮將兩者結合起來形成一種新的數據結構 LinkedHashMap。

LRU 算法的核心數據結構就是哈希鏈表,它是雙向鏈表和哈希表的結合體。其具體數據結構如下圖所示:

Java 手寫LRU緩存淘汰算法

借助這個數據結構我們來注意分析上邊的條件:

如果每次默認從鏈表尾部添加元素,那么顯然越靠近尾部的元素越是最近使用的,越是靠近頭部的元素越是最久未被使用的。 對于某一個 key,可以通過哈希表快速定位到對應的 val 上 鏈表顯然支持快速插入和快速刪除。 方法一

在 Java 中本身是有 LinkedHashMap 這個數據結構的,但是為了了解算法的細節,我們嘗試自己實現一遍 LRU 算法。

首先我們需要定義一個雙向鏈表,為了簡化,key 和 val 都設置稱 int 類型。

class Node { public int key,val; public Node next, pre; public Node(int key, int val) {this.key = key;this.val = val; }}//構建一個雙向鏈表,實現一個LRU算法必須的APIclass DoubleList{ //頭尾虛節點 private Node head, tail; //用來記錄鏈表元素數量 private int size; //初始化鏈表 public DoubleList() { head = new Node(0, 0); tail = new Node(0, 0); head.next = tail; tail.pre = head; size = 0;} //從尾部添加一個元素 public Node addLast(Node x) { x.pre = tail.pre; x.next = tail; tail.pre.next = x; tail.pre = x; size++; return x; } //刪除某一個元素(x必定存在于雙向鏈表中) public Node remove(Node x) { x.pre.next = x.next; x.next.pre = x.pre; size--; return x; } //刪除第一個元素 public Node removeFirst() { //判斷當前size是否為空 if(head.next == tail) { return null; } return remove(head.next); } //返回鏈表長度 public int size() { return this.size; }}

有了雙向鏈表,只需要在 LRU 算法的基礎上把它和 HashMap 結合起來就可以打出整個算法的一個基本框架。

class LRUCache {private HashMap<Integer,Node> map;private DoubleList cache;private int capacity; public LRUCache(int capacity) { this.capacity = capacity; map = new HashMap<>(); cache = new DoubleList(); } public int get(int key) {//具體實現 } public void put(int key, int value) {//具體實現 }}

由于要同時維護一個雙向鏈表 cache 和一個哈希表 map,在編寫的過程中容易漏掉一些操作,因而我們可以**在這兩種數據結構的基礎上,抽象出一層 API。**盡量避免 get 和 put 操作直接操作 map 和 cache 的細節。

//封裝HashMap和鏈表組合在一起常用的一些操作//將某一個key提升為最近使用private void makeRecently(int key) { // ????? 不需要對map中key和Node的映射關系進行維護嗎? //cache 本身地址并沒有變化所以不需要重新來維護key和Node的關系 Node x = map.get(key); cache.remove(x); cache.addLast(x);}//添加最近使用的元素private void addRecently(int key, int val) { Node x = new Node(key,val); cache.addLast(x); map.put(key, x);}//刪除某一個keyprivate void deleteKey(int key) { Node x = map.get(key); //從鏈表中刪除節點 cache.remove(x); //刪除key->x的映射關系 map.remove(key);}//刪除最久未使用元素private void removeLeastRecently() { //刪除鏈表中的第一個節點 Node deleteNode = cache.removeFirst(); //刪除map中的映射關系 map.remove(deleteNode.key);}

進而我們便可以寫出完整的代碼:

import java.util.HashMap;/**方法一:不使用LinkedHashMap,完全從雙向鏈表開始寫**/class LRUCache {private HashMap<Integer,Node> map;private DoubleList cache;private int capacity; public LRUCache(int capacity) { this.capacity = capacity; map = new HashMap<>(); cache = new DoubleList(); } public int get(int key) { if(!map.containsKey(key)) { return -1; } makeRecently(key); return map.get(key).val; } public void put(int key, int value) { //該節點已經存在 if(map.containsKey(key)) { deleteKey(key); addRecently(key, value); return; } if(capacity == cache.size()) { removeLeastRecently(); } //添加為最近使用的元素 addRecently(key, value); } //封裝HashMap和鏈表組合在一起常用的一些操作 //將某一個key提升為最近使用 private void makeRecently(int key) {// ????? 不需要對map中key和Node的映射關系進行維護嗎?//cache 本身地址并沒有變化所以不需要重新來維護key和Node的關系 Node x = map.get(key); cache.remove(x); cache.addLast(x); } //添加最近使用的元素 private void addRecently(int key, int val) { Node x = new Node(key,val); cache.addLast(x); map.put(key, x); } //刪除某一個key private void deleteKey(int key) { Node x = map.get(key); //從鏈表中刪除節點 cache.remove(x); //刪除key->x的映射關系 map.remove(key); } //刪除最久未使用元素 private void removeLeastRecently() { //刪除鏈表中的第一個節點 Node deleteNode = cache.removeFirst(); //刪除map中的映射關系 map.remove(deleteNode.key); }}class Node { public int key,val; public Node next, pre; public Node(int key, int val) {this.key = key;this.val = val; }}//構建一個雙向鏈表,實現一個LRU算法必須的APIclass DoubleList{ //頭尾虛節點 private Node head, tail; //用來記錄鏈表元素數量 private int size; //初始化鏈表 public DoubleList() { head = new Node(0, 0); tail = new Node(0, 0); head.next = tail; tail.pre = head; size = 0;} //從尾部添加一個元素 public Node addLast(Node x) { x.pre = tail.pre; x.next = tail; tail.pre.next = x; tail.pre = x; size++; return x; } //刪除某一個元素(x必定存在于雙向鏈表中) public Node remove(Node x) { x.pre.next = x.next; x.next.pre = x.pre; size--; return x; } //刪除第一個元素 public Node removeFirst() { //判斷當前size是否為空 if(head.next == tail) { return null; } return remove(head.next); } //返回鏈表長度 public int size() { return this.size; }}

Java 手寫LRU緩存淘汰算法

至此,我們已經完全掌握了 LRU 算法的原理和實現了,最后我們可以通過 Java 內置的類型 LinkedHashMap 來實現以下 LRU 算法。

方法二

在正式編寫之前,我們簡單說是說這個 LinkedHashMap。

LinkedHashMap 是 HashMap 的子類,但內部還有一個雙向鏈表維護者鍵值對的順序;每一個鍵值對即位于哈希表中,也存在于這個雙向鏈表中。LinkedHashMap 支持兩種順序:第一種是插入順序,另外一種是訪問順序。

插入順序,比較容易理解,先添加的元素在前邊,后添加的元素在后邊,修改和訪問操作并不改變元素在鏈表中的順序。那訪問順序是什么意思那?所謂訪問指的就是 put/get 操作,對于一個 key 執行 get/put 操作之后,對應的鍵值對就會移動到鏈表尾部。所以鏈表尾部就是最近訪問的,最開始的就是最久沒被訪問的。

因此最簡單的方法就是在創建一個 LinkedHashMap 時直接指定訪問順序和容量。此后直接操作 LinkedHashMap 即可。

具體代碼如下:

import java.util.LinkedHashMap;import java.util.Map.Entry;class LRUCache {MyLRUCache<Integer,Integer> cache; public LRUCache(int capacity) { cache = new MyLRUCache(capacity); } public int get(int key) {if(cache.get(key) == null) { return -1;} return cache.get(key); } public void put(int key, int value) { cache.put(key, value); }}class MyLRUCache<K,V> extends LinkedHashMap<K,V> { private int capacity; public MyLRUCache(int capacity) {//指定初始容量,增長因子,指定訪問順序super(16, 0.75f, true);this.capacity = capacity; } //由于LinkedHahsMap本身是不支持容量限制,我們可以成通過重寫removeEldestEntry,使得容量大于預定容量時,刪除頭部的元素 @Overrideprotected boolean removeEldestEntry(Entry<K, V> eldest) { return size() > capacity;}}

Java 手寫LRU緩存淘汰算法

方法三

由于方法二需要通過重寫 removeEldestEntry 方法來實現緩存,在面試的時候不容易想到,因此我們考慮只是用 LinkedHashMap 的插入順序,增加若干操作來實現 LRU 緩存。

class LRUCache { int capacity; LinkedHashMap<Integer,Integer> cache; public LRUCache(int capacity) {this.capacity = capacity;cache = new LinkedHashMap<>(); } public int get(int key) {if(!cache.containsKey(key)) { return -1;}makeRecently(key);return cache.get(key); } public void put(int key, int value) {if(cache.containsKey(key)) { //修改value的值 cache.put(key,value); makeRecently(key); return;}if(cache.size() >= this.capacity) { //鏈表頭部是最久未被使用的key int oldestKey = cache.keySet().iterator().next(); cache.remove(oldestKey);}cache.put(key,value); } private void makeRecently(int key) {int val = cache.get(key);cache.remove(key);cache.put(key,val); }}

Java 手寫LRU緩存淘汰算法

總結

本文主要講了如何通過哈希鏈表這種數據結構來實現 LRU 算法,提供了三種實現思路,第一種從雙向鏈表開始,借助于 HashMap 來實現滿足要求的 LRUCache,后兩種針對 LinkedHashMap 的不同順序,設計了兩種實現方式來實現 LRUCache。

以上就是Java 手寫LRU緩存淘汰算法的詳細內容,更多關于Java 寫LRU緩存淘汰算法的資料請關注好吧啦網其它相關文章!

標簽: Java
相關文章:
主站蜘蛛池模板: 风化石头制砂机_方解石制砂机_瓷砖石子制砂机_华盛铭厂家 | 外贸网站建设-外贸网站设计制作开发公司-外贸独立站建设【企术】 | 安全光栅|射频导纳物位开关|音叉料位计|雷达液位计|两级跑偏开关|双向拉绳开关-山东卓信机械有限公司 | 权威废金属|废塑料|废纸|废铜|废钢价格|再生资源回收行情报价中心-中废网 | 博博会2021_中国博物馆及相关产品与技术博览会【博博会】 | 假肢-假肢价格-假肢厂家-河南假肢-郑州市力康假肢矫形器有限公司 | 线材成型机,线材折弯机,线材成型机厂家,贝朗自动化设备有限公司1 | 档案密集架,移动密集架,手摇式密集架,吉林档案密集架-厂家直销★价格公道★质量保证 | 建筑资质代办-建筑企业资质代办机构-建筑资质代办公司 | 石英粉,滑石粉厂家,山东滑石粉-莱州市向阳滑石粉有限公司 | 芝麻黑-芝麻黑石材厂家-永峰石业| 数码听觉统合训练系统-儿童感觉-早期言语评估与训练系统-北京鑫泰盛世科技发展有限公司 | 飞扬动力官网-广告公司管理软件,广告公司管理系统,喷绘写真条幅制作管理软件,广告公司ERP系统 | 精密线材测试仪-电线电缆检测仪-苏州欣硕电子科技有限公司 | 三佳互联一站式网站建设服务|网站开发|网站设计|网站搭建服务商 赛默飞Thermo veritiproPCR仪|ProFlex3 x 32PCR系统|Countess3细胞计数仪|371|3111二氧化碳培养箱|Mirco17R|Mirco21R离心机|仟诺生物 | 深圳VI设计-画册设计-LOGO设计-包装设计-品牌策划公司-[智睿画册设计公司] | 微动开关厂家-东莞市德沃电子科技有限公司 | 管家婆-管家婆软件-管家婆辉煌-管家婆进销存-管家婆工贸ERP | nalgene洗瓶,nalgene量筒,nalgene窄口瓶,nalgene放水口大瓶,浙江省nalgene代理-杭州雷琪实验器材有限公司 | 口臭的治疗方法,口臭怎么办,怎么除口臭,口臭的原因-口臭治疗网 | 即用型透析袋,透析袋夹子,药敏纸片,L型涂布棒-上海桥星贸易有限公司 | 铝合金脚手架厂家-专注高空作业平台-深圳腾达安全科技 | 经济师考试_2025中级经济师报名时间_报名入口_考试时间_华课网校经济师培训网站 | 成都热收缩包装机_袖口式膜包机_高速塑封机价格_全自动封切机器_大型套膜机厂家 | 除尘器布袋骨架,除尘器滤袋,除尘器骨架,电磁脉冲阀膜片,卸灰阀,螺旋输送机-泊头市天润环保机械设备有限公司 | 智能交通网_智能交通系统_ITS_交通监控_卫星导航_智能交通行业 | 酒精检测棒,数显温湿度计,酒安酒精测试仪,酒精检测仪,呼气式酒精检测仪-郑州欧诺仪器有限公司 | 沈阳缠绕包装机厂家直销-沈阳海鹞托盘缠绕包装机价格 | PE一体化污水处理设备_地埋式生活污水净化槽定制厂家-岩康塑业 | 污水提升器,污水提升泵,地下室排水,增压泵,雨水泵,智能供排水控制器-上海智流泵业有限公司 | Jaeaiot捷易科技-英伟达AI显卡模组/GPU整机服务器供应商 | 丝杆升降机-不锈钢丝杆升降机-非标定制丝杆升降机厂家-山东鑫光减速机有限公司 | 吹田功率计-长创耐压测试仪-深圳市新朗普电子科技有限公司 | 九爱图纸|机械CAD图纸下载交流中心| 列管冷凝器,刮板蒸发器,外盘管反应釜厂家-无锡曼旺化工设备有限公司 | 自动螺旋上料机厂家价格-斗式提升机定制-螺杆绞龙输送机-杰凯上料机 | PCB厂|线路板厂|深圳线路板厂|软硬结合板厂|电路板生产厂家|线路板|深圳电路板厂家|铝基板厂家|深联电路-专业生产PCB研发制造 | 不锈钢/气体/液体玻璃转子流量计(防腐,选型,规格)-常州天晟热工仪表有限公司【官网】 | 找培训机构_找学习课程_励普教育 | 冷轧机|两肋冷轧机|扁钢冷轧机|倒立式拉丝机|钢筋拔丝机|收线机-巩义市华瑞重工机械制造有限公司 | 400电话_400电话申请_888元包年_400电话办理服务中心_400VIP网 |