java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现
java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现
- java基础解析系列(一)---String、StringBuffer、StringBuilder
- java基础解析系列(二)---Integer
- java基础解析系列(三)---HashMap
- 这是我的博客目录,欢迎阅读
实验
遍历HashMap
public static void main(String[] args)
{
Map<String, String> map=new HashMap<String,String>();
map.put("white", "小白");
map.put("black", "小黑");
map.put("red", "小红");
map.put("yellow", "小黄");
map.get("yellow");
map.get("red");
map.get("black");
map.get("white");
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
- 结果发现,hashmap遍历出来的是无序的
换成LinkedHashMap
Map<String, String> map=new LinkedHashMap<String,String>();
- 结果发现,遍历出来的顺序是按找插入的顺序
改变LinkedHashmap的参数
Map<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true);
- 结果发现,遍历出来的顺序是按照访问的顺序的来的
分析
与HashMap的关系
- LinkedHashMap可以看做是LinkedList+hashmap的组合
- LinkedHashMap是hashmap的子类
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
- LinkedHashMap中的Entry有before和after两个域,这是用来实现双向链表的
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
构造方法
LinkedHashMap
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder)构造一个带指定初始容量、加载因子和排序模式的空 LinkedHashMap 实例。
参数:
initialCapacity - 初始容量
loadFactor - 加载因子
accessOrder - 排序模式 - 对于访问顺序,为 true;对于插入顺序,则为 false
抛出:
IllegalArgumentException - 如果初始容量为负或者加载因子为非正
- 从api可以看出,有一个参数accessOrder,默认情况下该参数为false,为false的时候,顺序为插入顺序,如果为true的话,顺序为访问顺序
public LinkedHashMap() {
super();
accessOrder = false;
}
void init() {
header = new Entry<K,V>(-1, null, null, null);
header.before = header.after = header;
}
- 从这里看出,还未put的时候就创建了一个header
put方法
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
//如果table数组该下标的内容不为空,遍历链表
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;
}
}
//如果没有该key
modCount++;
addEntry(hash, key, value, i);
return null;
}
- put 方法继承自hashmap的,不同的是addEntry被重写了,如果put的键值对中是新的,那么执行addEntry方法
addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
- addEntry方法先调用createEntry方法
createEntry方法
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
- 这里将Entry放到table之后,还执行了一个addBefore方法
addBefore方法
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
- 这个方法用于维护双向链表,可以自己画一个图看一下
回到addEntry方法,执行完createEntry方法后,下面的部分代码
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
- 第一句注释的意思是如果指示那么删除最年长的,否则扩容
- 如果removeEldestEntry返回true,那么删除老的键值对,否则的话如果超过阈值,进行扩容
removeEldestEnrey方法
protected boolean More removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
- removeEldestEntry方法,也就是是否删除最年长的Entry。这里始终返回的是false,我们可以对他进行重写
recordAccess方法
- 当put的是新的键值对,键是之前没有的话,那么会创建一个新的Entry,而如果是之前有的话,那么会覆盖value并执行revordAccess,中文意思为记录访问
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
private void remove() {
before.after = after;
after.before = before;
}
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
- 这个方法会调用remove方法,而这个方法实际上是改变链表的指针,执行后,相当于之前没有这个这个Entry(可以自己动手画一下指针指向),然后再将这个Entry重新加入,这个时候以前添加的Entry,由于最近被使用,就被移到到链表的后面了
- 同样使用get的时候也会调用此方法,也就是会由于被访问被移动到前面
总结
- 初始化一个LinkedHash,会创建一个header
- put的时候,将Entry放入数组中后,会和header连在一起
- 再put的一个的时候,同样加入数组后,会继续和前面一个相连,此时header,小黑,小红三个连在一起,
- 此时get小黑,小黑会从链表中移除,此时相当于只添加了小红,然后再将小黑添加进去,这样的话就保证了,最新访问的在后面
- 如果put的时候该键是之前已有的,那么会覆盖value,然后重新调整该Entry在链表中的位置
LRU实现
- 所谓LRU也就是Least Recently Used,最近最少使用,当缓存满了,删除最少使用的
- 那么LinkedHashMap适合于解决这个问题,因为他使用了双向链表,将最新访问的移到了后面,那么这样的话,前面的就是最少使用,这时候就可以将最少使用的删除
- removeEldestEntry,前面说过这方法,这个方法就是删除最少使用的,默认下不会删除,因为该方法返回false,所以当空间满了的话会扩容,我们可以重写这个方法,那么这样就不需要扩容,满了的时候删除最少使用的就可以
- 重写removeEldestEntry,当大小大于容量的时候,会删除最年长的键值对
public class LRUCache {
private int capacity;
private Map<Integer, Integer> cache;
public LRUCache(int capacity) {
this.capacity = capacity;
this.cache = new LinkedHashMap<Integer, Integer> (capacity, 0.75f, true) {
// 定义put后的移除规则,大于容量就删除eldest
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
};
}
public int get(int key) {
if (cache.containsKey(key)) {
return cache.get(key);
} else
return -1;
}
public void set(int key, int value) {
cache.put(key, value);
}
public static void main(String[] args) {
LRUCache cache=new LRUCache(2);
cache.set(1,1);
cache.set(2, 2);
//此时超出容量,put3的时候删除cache
cache.set(3, 3);
Set<Map.Entry<Integer,Integer>> set = cache.cache.entrySet();
Iterator<Map.Entry<Integer,Integer>> iterator = set.iterator();
while (iterator.hasNext())
{
System.out.print(iterator.next() + "\t");
}
System.out.println();
//输出2=2 3=3
}
}
我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)
作者:jiajun 出处: http://www.cnblogs.com/-new/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。
java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现的更多相关文章
- java基础解析系列(六)---深入注解原理及使用
java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ...
- java基础解析系列(七)---ThreadLocal原理分析
java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
- java基础解析系列(八)---fail-fast机制及CopyOnWriteArrayList的原理
fail-fast机制及CopyOnWriteArrayList的原理 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列( ...
- java基础解析系列(六)---注解原理及使用
java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ...
- java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别
java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别 目录 java基础解析系列(一)---String.StringBuffer.St ...
- java基础解析系列(九)---String不可变性分析
java基础解析系列(九)---String不可变性分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---In ...
- java基础解析系列(十)---ArrayList和LinkedList源码及使用分析
java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...
- java基础解析系列(十一)---equals、==和hashcode方法
java基础解析系列(十一)---equals.==和hashcode方法 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系 ...
- java基础解析系列(二)---Integer
java基础解析系列(二)---Integer 前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现bug的时候,才能处理更加从容. 目录 java基础解析 ...
随机推荐
- Java内存区域与对象创建过程
一.java内存区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则 ...
- 【CSS】less 学习小结
1. less 使用 less 可直接使用浏览器解析 or 使用node 的grunt/gulp 解析成传统css . 推荐开发环境直接使用less 文件调试, 生产环境部署解析好的css 2. l ...
- 【javascript】变量作用范围
一.全局变量&局部变量 test="" 全局变量 var test="" 局部变量,无块的概念,作用域为function 或者script块 二.有意思 ...
- OP查阅网站
1)http://www.zhdba.com/mysqlops/ 2)
- cookie原理
cookie原理 一般来说,Cookie通过HTTP Headers从服务器端返回到浏览器上.首先,服务器端在响应中利用Set-Cookie header来创建一个Cookie,然后,浏览器在它的请求 ...
- 一步一步学Vue(九)
接上篇,这次是真的接上篇,针对上篇未完成的部分,增加鉴权功能,开始之前,我们先要介绍一个新的知识,路由元数据. 在vue-router中,定义元数据的方式: const router = new Vu ...
- Java 9 揭秘(20. JDK 9中API层次的改变)
Tips 做一个终身学习的人. 在最后一章内容中,主要介绍以下内容: 下划线作为新关键字 改进使用try-with-resources块的语法 如何在匿名类中使用<>操作符 如何在接口中使 ...
- 前端数据存储方案集合(cookie localStorage等)以及详解 (一)
客户端.前端 存储 一. 起 因 首先解释下为什么想来写这个关于前端存储的问题,因为最近在做小程序相关的内容.但是,在开发过程中,我们难免会遇到 token 存储. 代码缓存. 图片存储等等. 以及可 ...
- 关于戴尔没有活动分区,遇到了“Windows安装程序无法将windows配置为在此计算机的硬件上运行”提示等
虽然只有几句话,但是还是超级好用的 装过很多戴尔的笔记本,发现很多都是这里的问题导致系统装不进去. 1.硬盘设置有问题.进BIOS ,到SATA 设置看看硬盘是不是设置为AHCI了.这个ghost系统 ...
- jQuery防京东浮动网站楼层导航代码
jQuery防京东浮动网站楼层导航代码 <!DOCTYPE html > <html xmlns="http://www.w3.org/1999/xhtml" ...