java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现

实验

遍历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算法的实现的更多相关文章

  1. java基础解析系列(六)---深入注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ...

  2. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

  3. java基础解析系列(八)---fail-fast机制及CopyOnWriteArrayList的原理

    fail-fast机制及CopyOnWriteArrayList的原理 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列( ...

  4. java基础解析系列(六)---注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ...

  5. java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别

    java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别 目录 java基础解析系列(一)---String.StringBuffer.St ...

  6. java基础解析系列(九)---String不可变性分析

    java基础解析系列(九)---String不可变性分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---In ...

  7. java基础解析系列(十)---ArrayList和LinkedList源码及使用分析

    java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...

  8. java基础解析系列(十一)---equals、==和hashcode方法

    java基础解析系列(十一)---equals.==和hashcode方法 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系 ...

  9. java基础解析系列(二)---Integer

    java基础解析系列(二)---Integer 前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现bug的时候,才能处理更加从容. 目录 java基础解析 ...

随机推荐

  1. Scrapy提取多个标签的text

    对于要提取嵌套标签所有内容的情况, 使用string或//text(), 注意两者区别 >>> from scrapy import Selector >>> &g ...

  2. 利用Apache commons-net 包进行FTP文件和文件夹的上传与下载

    首先声明:这段代码是我在网上胡乱找的,调试后可用. 需要提前导入jar包,我导入的是:commons-net-3.0.1,在网上可以下载到.以下为源码,其中文件夹的下载存在问题:FTPFile[] a ...

  3. string::npos,一个很大的数

    string::npos,这是一个很大的数 npos 是这样定义的: static const size_type npos = -1; 因为 string::size_type (由字符串配置器 a ...

  4. Git异常情况汇总

    本篇博客总结下Git使用情况中遇到的异常情况并给出解决方案,关于Git的常用命令请移步我的另一篇博客<Git常用命令> 异常情况如下: 1.git远程删除分支后,本地git branch ...

  5. 156个Python网络爬虫资源

    本列表包含Python网页抓取和数据处理相关的库. 网络相关 通用 urllib - 网络库(标准库) requests - 网络库 grab - 网络库(基于pycurl) pycurl - 网络库 ...

  6. 国内阿里Maven仓库镜像及自己收集镜像库

    国内阿里Maven仓库镜像Maven配置文件Maven仓库速度快   国内连接maven官方的仓库更新依赖库,网速一般很慢,收集一些国内快速的maven仓库镜像以备用. 最新更新:2016年11月11 ...

  7. 一次关于mongodb性能踩坑的总结

    发现性能问题 上一次导入数据后,发现系统十分的卡顿,但是才仅仅1000多条数据而已,怎么会让系统变得如何的卡顿呢?于是我开始走在排查系统卡顿的原因的道路上. 首先,先定位问题是出现在前端上还是后端上. ...

  8. 【有意思的BUG】默认置灰的属性的值传递

    数据由Client发送给Server,如下图所示,Server在收到请求后会响应Client. 比如Client发送给Server一组数据:Name:sean&Hobby:movies& ...

  9. 打鼹鼠 HNOI 2004

    动态规划题从题目中可以发现是一个时间递增的过程,所以只要是在后面的点都是后出现的,换句话说,在条件达成时,前面的点可以到达后面的点,求最长的一条链,非常的像LIS(最长上升子序列),只要将 f[i]& ...

  10. Orleans稍微复杂的例子—互动

    这是Orleans系列文章中的一篇.首篇文章在此 我费力费心的翻译过官方的教程,但是本人英语词汇量不高,可是架不住电子词典啊-只要肯花时间,我这些内容谁都可以做出来.所以这个事例告诉我们一个道理,那就 ...