当前位置: 首页 > news >正文

武汉网站服务站长工具平台

武汉网站服务,站长工具平台,什么是外包,dw网站制作流程问题:使用Java完成一个简单的LRU算法什么是LRU算法LRU(Least Recently Used),也就是最近最少使用。一种有限的空间资源管理的解决方案,会在空间资源不足的情况下移除掉最近没被使用过的数据,以保证接下来需要的空间资源。在现在通…

问题:使用Java完成一个简单的LRU算法

什么是LRU算法

LRU(Least Recently Used),也就是最近最少使用。一种有限的空间资源管理的解决方案,会在空间资源不足的情况下移除掉最近没被使用过的数据,以保证接下来需要的空间资源。

在现在通用的操作系统中为了解决内存不足这个问题,提出了虚拟内存这种解决方案,其实虚拟内存也就是将机器的内存分为多个页面(提个小问题,一个页面包含了多少kb的空间?),内存中只存放当前需要的页面信息,暂时不使用的内存数据就保存到磁盘中。这可以很好的解决内存不足的问题。当然了这就无故出现页面交换的情况,使得读取内存的速度降低(磁盘的读取速度远小于内存的读取速度),这种方案肯定有利有弊,只需要我们的服务能够接受这种情况,那就完全没有问题。

Redis做为一种内存数据库,内存大小对数据库的影响更重要,所以redis也需要及时的移除掉那些过期数据。在redis中有定时清楚、惰性删除、定期删除,但是其策略主要分为两种,基于访问时间和基于访问频率。基于访问时间就是LRU算法,看看LRU算法的图解过程,如下图。

e26a2301f97b

image.png

先定义好一个定长的队列

按照FIFO的流程,依次申请一段空间

直到队列被占满了,出现内存不足的情况,淘汰策略开始工作

会淘汰队列中最先进入的数据,最先进去的数据也就是最近最久未被使用的数据,然后把其移除出队列

LRU 算法小demo

整体的算法实现没有太多的难度,维护一个有限长度的队列的进出,需要移除或者插入数据。时间复杂度可能会是个问题。

队列如果是链表,则移除数据的时间复杂度是O(1),但是查找数据的时间复杂度是O(n)

队列如果是数组,则移除数据的时间复杂度是O(n),而且移除数据还伴随着数组的平行移动,查找数据也是O(n),除非另外再加一个Map存储其索引值会使得其查找的速度降低到O(1),但是却又提高了空间复杂度

接下来写个基于数组的LRU的简单代码

public class LruDemo {

private Object[] items;

private HashMap map;

private int size;

private int index;

public LruDemo() {

this(8);

}

public LruDemo(int size) {

this.size = size;

this.items = new Object[size];

this.map = new HashMap<>(16);

this.index = 0;

}

public void put(T t) {

Integer value = map.get(t);

if (value == null) {

if (index >= size) {

// 满了,需要移除第一个元素

for(int i=1; i

items[i-1] = items[i];

map.replace((T)items[i-1], i);

}

index -= 1;

}

items[index] = t;

map.put(t, index);

index += 1;

} else {

for(int i=value; i

items[i] = items[i+1];

map.replace((T)items[i], i);

}

items[index-1] = t;

map.replace(t, index-1);

}

}

public void getAll() {

for(int i=0;i

System.out.println(items[i]);

}

System.out.println("======");

}

public static void main(String[] args) {

LruDemo lruDemo = new LruDemo(6);

lruDemo.put("aliace");

lruDemo.put("bob");

lruDemo.put("cat");

lruDemo.put("dog");

lruDemo.put("egg");

lruDemo.getAll();

lruDemo.put("bob");

lruDemo.getAll();

lruDemo.put("fine");

lruDemo.put("good");

lruDemo.getAll();

}

}

输出的结果是

aliace

bob

cat

dog

egg

======

aliace

cat

dog

egg

bob

======

cat

dog

egg

bob

fine

good

======

这只是一种简单的写法,而且效率也比较低,现在就来介绍下将要学习的LinkedHashMap

LinkedHashMap

LinkedHashMap是继承自HashMap的,只是另外添加了排序相关的功能,使得其成为了有序hashmap,关于HashMap的介绍可以看看Java8的HashMap原理学习,接下来重点关注LinkedHashMap相比HashMap拓展了哪些功能呢?

Entry节点信息

static class Entry extends HashMap.Node {

Entry before, after;

Entry(int hash, K key, V value, Node next) {

super(hash, key, value, next);

}

}

头尾节点

transient LinkedHashMap.Entry head;

transient LinkedHashMap.Entry tail;

Entry节点就包含了前置节点和后置节点的地址信息,再加上在LinkedHashMap又中添加了head和tail头尾节点,这样就使得之前的链表+数据的数据结构基础上又加上了双向链表,通过双向链表实现有序性,并且 LinkedHashMap = Linked + HashMap。

accessOrder 值

final boolean accessOrder; 是一个非常关键的字段值,暂时按下不表,接下来会知道其真正的含义

get操作

HashMap进行get操作还是很简单的,通过hash获取index,再可能涉及到链表(红黑树)的遍历操作,在LinkedHashMap中同样重写了相关方法

public V get(Object key) {

Node e;

if ((e = getNode(hash(key), key)) == null)

return null;

if (accessOrder)

afterNodeAccess(e);

return e.value;

}

进行常规的getNode操作后在找到对应的节点e之后,当accessOrder是true时,调用afterNodeAccess方法,从其名称也可以看出来时访问节点后的操作。

void afterNodeAccess(Node e) { // move node to last

LinkedHashMap.Entry last;

if (accessOrder && (last = tail) != e) {

LinkedHashMap.Entry p =

(LinkedHashMap.Entry)e, b = p.before, a = p.after;

// b 和 a 分别是访问的节点e的前置和后置节点

p.after = null;

if (b == null)

head = a;

else

b.after = a;

if (a != null)

a.before = b;

else

last = b;

if (last == null)

head = p;

else {

p.before = last;

last.after = p;

}

// 把其移动到双向链表的尾部节点

tail = p;

++modCount;

}

}

也就是说当accessOrder为true时,会修改其双向链表的节点顺序,而且搜索整个类也会发现accessOrder只在这里发挥用处。顺带观察下其key、value、entry的迭代器遍历情况,可以发现都是使用了for (LinkedHashMap.Entry e = head; e != null; e = e.after) 这种条件去循环遍历。

所以accessOrder就是起到控制访问顺序的作用,设置为true之后每访问一个元素,就将该元素移动到双向链表的尾部节点,通过改变节点在双向链表的位置实现对链表顺序的控制。

put 操作

在HashMap中通过put方法插入一个新的节点数据,LinkedHashMap并没有重写该方法。在HashMap中先检查是否存在对应的key,如果不存在则会通过newNode方法创建一个新节点,然后等待插入到合适的位置,LinkedHashMap则重写了newNode方法,如下代码块:

Node newNode(int hash, K key, V value, Node e) {

LinkedHashMap.Entry p =

new LinkedHashMap.Entry(hash, key, value, e);

linkNodeLast(p);

return p;

}

// link at the end of list

private void linkNodeLast(LinkedHashMap.Entry p) {

LinkedHashMap.Entry last = tail;

tail = p;

if (last == null)

head = p;

else {

p.before = last;

last.after = p;

}

}

创建完一个LinkedHashMap.Entry节点p后,p节点的before, after都是null,然后调用linkNodeLast方法,采取尾插法,形成新的尾节点(这里有一种情况是最早的时候tail==head==null的情况,会使得头节点和尾节点都指向同一个节点)。

新插入一个节点后还会调用afterNodeInsertion方法,看起方法名称也知道是在node节点插入后的操作,在HashMap中是空实现,在LinkedHashMap则实现了该方法,如下代码块:

void afterNodeInsertion(boolean evict) { // possibly remove eldest

LinkedHashMap.Entry first;

if (evict && (first = head) != null && removeEldestEntry(first)) {

K key = first.key;

removeNode(hash(key), key, null, false, true);

}

}

protected boolean removeEldestEntry(Map.Entry eldest) {

return false;

}

默认传入的evict值是true,而removeEldestEntry方法默认返回false,也就是什么都不做。当在put一个已经存在的节点的情况,会调用afterNodeAccess方法,也会去改变在链表中的位置。重写removeEldestEntry方法并且当起返回true时,调用removeNode节点移除head节点。这个就包含了LRU最近最少使用的实现原理。

自定义LRU算法

设置accessOrder为true后,每次访问、新增的节点都会移动到尾部,当removeEldestEntry返回true时就会移除头节点,那么只需要设置一种特定的判断逻辑使得removeEldestEntry返回true就可以了。按照上面LRU算法的思想,只有当空间满了的情况下才会移除头节点数据,同理只需要判断当前map中的节点数是否达到相关的阈值即可。继承LinkedHashMap重载removeEldestEntry方法,代码如下:

public class LruMap extends LinkedHashMap {

private int maxSize;

public LruMap(int initialCapacity, float loadFactor, boolean accessOrder, int maxSize) {

super(initialCapacity, loadFactor, accessOrder);

this.maxSize = maxSize;

}

public LruMap(int maxSize) {

this(16, 0.75f, true, maxSize);

}

public LruMap(int tableSize, int maxSize) {

this(tableSize, 0.75f, true, maxSize);

}

@Override

protected boolean removeEldestEntry(Map.Entry eldest) {

boolean flag = size() > maxSize;

if (flag) {

System.out.println("移除头节点, key:" + eldest.getKey() + ", value:" + eldest.getValue());

}

return flag;

}

public static void main(String[] args) {

LruMap lruMap = new LruMap<>(4, 4);

lruMap.put(1, "aaaa");

lruMap.put(2, "bbbb");

lruMap.put(3, "cccc");

lruMap.put(4, "dddd");

lruMap.put(5, "eeee");

lruMap.entrySet().forEach(node -> {

System.out.println("key:" + node.getKey() + ", value:" + node.getValue());

});

}

}

运行结果如下图,测试了table大小时4个,当插入了1~4 4个节点后,再插入5这个节点时,会移除头节点也就是节点1,从输出的日志也很清楚了。

e26a2301f97b

image

如果利用get方法改变一下双向链表的顺序,可以控制移除的节点,修改一下测试数据,如下代码块

lruMap.put(1, "aaaa");

lruMap.put(2, "bbbb");

lruMap.put(3, "cccc");

lruMap.get(1);

lruMap.get(2);

lruMap.put(4, "dddd");

lruMap.put(5, "eeee");

这时候访问节点1和2,就会使得1和2移动到双向链表的尾部,头节点就是节点3,所以移除的头节点肯定是节点3,如下图符合我们的设想。

e26a2301f97b

image

总结

本篇学习笔记主要是介绍了LRU算法和LinkedHashMap,并且根据LinkedHashMap的功能实现一个简单的LRU算法,关于LinkedHashMap只需要了解到accessOrder值和双向链表的顺序有关,而LRU删除节点则是在每次插入之后确认是否达到某种需要移除节点的条件。

http://www.lbrq.cn/news/2598013.html

相关文章:

  • 学校网站模板html线上营销手段有哪些
  • 建筑网站do网站页面设计模板
  • 网站建设第三方平台网络推广有效果吗
  • 做网站在手机显示怎么很乱国外媒体报道
  • 上海新媒体运营公司排名厦门seo总部电话
  • 镇江做网站哪家公司好网络营销做得比较好的企业
  • 红河企业网络推广外包手机优化助手
  • 网站几几年做的怎么查百度竞价开户3000
  • 网站服务器出错是什么意思十大新媒体平台有哪些
  • 广州天河区建设网站公司推广
  • 徐州网站建设方案咨询如何快速搭建网站
  • 三合一网站是什么广东seo推广
  • 自已能做网站建设吗网站优化排名软件网
  • 在哪个网站可以做外单衣服网站的推广方案的内容有哪些
  • 信誉好的盐城网站开发搜索引擎优化教材答案
  • 去哪找网站建设公司游戏代理推广渠道
  • 做违法网站媒体邀约
  • 怎样做企业网站建设深圳seo网络优化公司
  • 上海做网站大的公司站长统计网站统计
  • 专业做网站建设公司seo关键词排名优化报价
  • wordpress网站导航菜单插件百度域名查询官网
  • 手机触屏网站幻灯片优化推广关键词
  • 杨浦专业做网站关键词排名代做
  • 新建的网站如何做seo网络推广软文怎么写
  • 微信网站跳转链接怎么做怎么进行推广
  • 网站建设广州营销网站设计
  • 电子商务网站开发技术毕业论文百度首页广告多少钱
  • wordpress移动端底部添加菜单二十条优化疫情措施
  • 医院网站制作好吗合肥seo推广外包
  • 网站建设 成功案例seo自学网app
  • 工作相关: 预刷真值与人工标注的真值之间的关系 以及 真值与原始数据的关系,
  • k8s常见问题
  • Java基础-斗地主游戏
  • Go语言高并发价格监控系统设计
  • Redis——常用指令汇总指南(三)(哈希类型)
  • 江协科技STM32 14-1 WDG看门狗