java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现
2017-09-06 14:59
831 查看
java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现
java基础解析系列(一)---String、StringBuffer、StringBuilderjava基础解析系列(二)---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,会创建一个headerput的时候,将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基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现
- URL短地址压缩算法 微博短地址原理解析 (Java实现)
- 深入Java集合学习系列:LinkedHashMap的实现原理
- URL短地址压缩算法 微博短地址原理解析 (Java实现)
- 基于LinkedHashMap实现LRU缓存调度算法原理
- 深入Java集合学习系列:LinkedHashMap的实现原理
- 深入Java集合学习系列:LinkedHashMap的实现原理
- 【系列】重新认识Java——泛型(基础、使用和实现原理)
- 深入Java集合学习系列:LinkedHashMap的实现原理
- URL短地址压缩算法 微博短地址原理解析 (Java实现)
- 深入Java集合学习系列:LinkedHashMap的实现原理
- 深入Java集合学习系列:LinkedHashMap的实现原理
- URL短地址压缩算法 微博短地址原理解析 (Java实现)
- 深入Java集合学习系列:LinkedHashMap的实现原理
- URL短地址压缩算法 微博短地址原理解析 (Java实现)
- 深入Java集合学习系列:LinkedHashMap的实现原理
- 深入Java集合学习系列:LinkedHashMap的实现原理
- 深入Java集合学习系列:LinkedHashMap的实现原理
- URL短地址压缩算法 微博短地址原理解析(再转与Java实现)