linkhashmap原理_LinkedHashMap原理分析

本文介绍了LinkedHashMap的工作原理,包括其有序特性和如何根据访问顺序进行排序。通过一个示例展示了如何使用LinkedHashMap实现Lru算法,强调了accessOrder参数的作用,并解释了removeEldestEntry方法在实现缓存淘汰策略中的关键作用。
摘要由CSDN通过智能技术生成

本文主要内容

LinkedHashMap使用

LinkedHashMap源码解析

Lru算法

今天打算看看android的图片缓存相关知识点,没想到引申到了LinkedHashMap容器了。

LinkedHashMap继承HashMap,相比于HashMap的无序,它是有序的,甚至它还能根据访问顺序输出内部存储元素,正是因为此特性,Lru(近期最少使用)算法一般由LinkedHashMap实现,尤其适合缓存算法。

LinkedHashMap使用

先来看看LinkedHashMap是如何使用的,以及它的神奇效果。

public class Lru extends LinkedHashMap {

public Lru(int initialCapacity,

float loadFactor,

boolean accessOrder){

super(initialCapacity, loadFactor, accessOrder);

}

@Override

protected boolean removeEldestEntry(java.util.Map.Entry eldest) {

if(size() > 6){

return true;

}

return false;

}

public static void main(String[] args) {

Lru lru = new Lru(16, 0.75f, true);

String s = "abcdefghijkl";

for (int i = 0; i < s.length(); i++) {

lru.put(s.charAt(i), i);

}

System.out.println("LRU中key为h的Entry的值为: " + lru.get('h'));

System.out.println("LRU的大小 :" + lru.size());

System.out.println("LRU :" + lru);

}

}

它的输出结果是:

LRU中key为h的Entry的值为: 7

LRU的大小 :6

LRU :{g=6, i=8, j=9, k=10, l=11, h=7}

代码中的构造方法,前两个在HashMap中见过,可参照HashMap源码解析阅读。最后一个参数,accessOrder,非常神奇。

accessOrder,表示LinkedHashMap不按照插入顺序排序,而是按照访问顺序排序。

联系上述代码的输出结果,h落在了最后,这就是accessOrder元素的作用。

值得一提的是,HashMap就无序的,因为HashMap内的元素在数组中的位置是由元素的hash值决定的,并不是先插入就在位置的前面。但LinkedHashMap是有序的,默认按插入顺序排序。

那么,为什么LinkedHashMap这么神奇呢?

LinkedHashMap源码解析

LinkedHashMap中申明了两个成员变量:

private transient Entry header;

private final boolean accessOrder;

而且LinkedHashMap对Entry也重写了,Entry类中添加了两个成员变量 before, after。

private static class Entry extends HashMap.Entry {

Entry before, after;

}

从上边来看,LinkedHashMap不但继承了父类使用数组存储元素,hash定位,自己内部还保存着一个双向链表,正是因为此双向链表的存在,LinkedHashMap才能有序。

我们来看它的put方法,不过LinkedHashMap并没有重写put方法,所以它的插入方法仍然同HashMap一致:

//HashMap的put方法

public V put(K key, V value) {

//省略中间过程

addEntry(hash, key, value, i);

}

但LinkedHashMap重写了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 eldest = header.after;

if (removeEldestEntry(eldest)) {

removeEntryForKey(eldest.key);

} else {

if (size >= threshold)

resize(2 * table.length);

}

}

查看createEntry方法:

void createEntry(int hash, K key, V value, int bucketIndex) {

HashMap.Entry old = table[bucketIndex];

Entry e = new Entry<>(hash, key, value, old);

table[bucketIndex] = e;

e.addBefore(header);

size++;

}

乍一看和HashMap很类似,但它额外调用了addBefore方法:

private void addBefore(Entry existingEntry) {

after = existingEntry;

before = existingEntry.before;

before.after = this;

after.before = this;

}

LinkedHashMap在初始化的时候,初始化header节点,header节点的after和before都是它自己。

void init() {

header = new Entry<>(-1, null, null, null);

header.before = header.after = header;

}

如果先后添加元素key0和key1,那么根据addBefore的逻辑,最终的连接情况如下:

如果 accessOrder 值为true时,当用户调用get方法时:

public V get(Object key) {

Entry e = (Entry)getEntry(key);

if (e == null)

return null;

e.recordAccess(this);

return e.value;

}

void recordAccess(HashMap m) {

LinkedHashMap lm = (LinkedHashMap)m;

if (lm.accessOrder) {

lm.modCount++;

remove();

addBefore(lm.header);

}

}

如果accessOrder为true,那么会将此节点原来的after和before节点重置,最后调用addBefore方法,节点位于双向链表的最后一位,类似于上图中的 key1。

回顾第1节关于LinkedHashMap的使用,最后打印LinkedHashMap,其实就是调用LinkedHashMap的toString方法,元素的排序非常有意思。

LinkedHashMap的toString方法是定义在AbstractMap类中:

//AbstractMap的toString方法

public String toString() {

Iterator> i = entrySet().iterator();

if (! i.hasNext())

return "{}";

StringBuilder sb = new StringBuilder();

sb.append('{');

for (;;) {

Entry e = i.next();

K key = e.getKey();

V value = e.getValue();

sb.append(key == this ? "(this Map)" : key);

sb.append('=');

sb.append(value == this ? "(this Map)" : value);

if (! i.hasNext())

return sb.append('}').toString();

sb.append(',').append(' ');

}

}

//HashMap类

//HashMap类中的newEntryIterator方法,返回一个Iterator对象

public Iterator> iterator() {

return newEntryIterator();

}

//LinkedHashMap类

//LinkedHashMap重写newEntryIterator方法,返回自己的Iterator对象,以便于符合自己风格的遍历

Iterator> newEntryIterator() { return new EntryIterator(); }

private class EntryIterator extends LinkedHashIterator> {

//LinkedHashMap返回的Iterator,最后调用nextEntry方法,实现Iterator接口,实现遍历

public Map.Entry next() { return nextEntry(); }

}

//以下代码是LinkedHashIterator类中的,初始化的nextEntry指向header的after

Entry nextEntry = header.after;

//nextEntry方法,就是在返回nextEntry对象的after节点

Entry nextEntry() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

if (nextEntry == header)

throw new NoSuchElementException();

Entry e = lastReturned = nextEntry;

nextEntry = e.after;

return e;

}

查看完以上代码,发现LinkedHashMap最终遍历时,其实是以header为第1个节点,不停地向后遍历after节点,所以第1章中产生的结果就会是那样,先插入的先显示出来,后插入的后显示,如果accessOrder为true,那么调用get方法得到的元素也会被插入到链表尾部,它也会被最后遍历。

Lru算法

Lru算法,即是 Least recently used,最近最少使用。使用LinkedHashMap可以轻易实现此算法。

当LinkedHashMap添加元素时:

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 eldest = header.after;

//如果removeEldestEntry方法返回为true,那么则删除1个节点

//默认就是删除离header节点最近的节点,即header的after,即链表的头部

if (removeEldestEntry(eldest)) {

removeEntryForKey(eldest.key);

} else {

if (size >= threshold)

resize(2 * table.length);

}

}

默认情况下removeEldestEntry方法返回为false,所以,如果要实现Lru算法,那么需要重写此方法,返回为true才行

protected boolean removeEldestEntry(Map.Entry eldest) {

return false;

}

当removeEldestEntry方法返回为true时,则每次都是删除离header最近的节点,回想起第1节,我们的测试代码结果也符合这一描述。

ps:没想到LinkedHashMap是这么有趣的一个容器,使用双向链表即可实现Lru算法,惊叹于Java的完美封装,LinkedHashMap能完美的继承于HashMap,同时还能有自己的特色,真是完美的代码,多向源码学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值