您的位置:首页 > 编程语言 > Java开发

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

2017-09-06 14:59 831 查看

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/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: