Android最全面试官:如何实现一个LruCache,原理是什么?,阿里、腾讯大厂Android面试必问知识点系统梳理

最后

感谢您的阅读,在文末给大家准备一个福利。本人从事Android开发已经有十余年,算是一名资深的移动开发架构师了吧。根据我的观察发现,对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

所以在此将我十年载,从萌新小白一步步成长为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。

当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

3.3 LruCache.get(K key)

/**

  • 根据 key 查询缓存,如果存在于缓存或者被 create 方法创建了。

  • 如果值返回了,那么它将被移动到双向循环链表的的尾部。

  • 如果如果没有缓存的值,则返回 null。

*/

public final V get(K key) {

V mapValue;

synchronized (this) {

// 关键点:LinkedHashMap每次get都会基于访问顺序来重整数据顺序

mapValue = map.get(key);

// 计算 命中次数

if (mapValue != null) {

hitCount++;

return mapValue;

}

// 计算 丢失次数

missCount++;

}

/*

  • 官方解释:

  • 尝试创建一个值,这可能需要很长时间,并且Map可能在create()返回的值时有所不同。如果在create()执行的时

  • 候,一个冲突的值被添加到Map,我们在Map中删除这个值,释放被创造的值。

*/

V createdValue = create(key);

if (createdValue == null) {

return null;

}

/***************************

  • 不覆写create方法走不到下面 *

***************************/

/*

  • 正常情况走不到这里

  • 走到这里的话 说明 实现了自定义的 create(K key) 逻辑

  • 因为默认的 create(K key) 逻辑为null

*/

synchronized (this) {

// 记录 create 的次数

createCount++;

// 将自定义create创建的值,放入LinkedHashMap中,如果key已经存在,会返回 之前相同key 的值

mapValue = map.put(key, createdValue);

// 如果之前存在相同key的value,即有冲突。

if (mapValue != null) {

/*

  • 有冲突

  • 所以 撤销 刚才的 操作

  • 将 之前相同key 的值 重新放回去

*/

map.put(key, mapValue);

} else {

// 拿到键值对,计算出在容量中的相对长度,然后加上

size += safeSizeOf(key, createdValue);

}

}

// 如果上面 判断出了 将要放入的值发生冲突

if (mapValue != null) {

/*

  • 刚才create的值被删除了,原来的 之前相同key 的值被重新添加回去了

  • 告诉 自定义 的 entryRemoved 方法

*/

entryRemoved(false, key, createdValue, mapValue);

return mapValue;

} else {

// 上面 进行了 size += 操作 所以这里要重整长度

trimToSize(maxSize);

return createdValue;

}

}

上述的 get 方法表面并没有看出哪里有实现了 LRU 的缓存策略。主要在 mapValue = map.get(key);里,调用了 LinkedHashMap 的 get 方法,再加上 LruCache 构造里默认设置 LinkedHashMap 的 accessOrder=true

3.4 LinkedHashMap.get(Object key)

/**

  • Returns the value of the mapping with the specified key.

  • @param key

  •        the key.
    
  • @return the value of the mapping with the specified key, or {@code null}

  •     if no mapping for the specified key is found.
    

*/

@Override public V get(Object key) {

/*

  • This method is overridden to eliminate the need for a polymorphic

  • invocation in superclass at the expense of code duplication.

*/

if (key == null) {

HashMapEntry<K, V> e = entryForNullKey;

if (e == null)

return null;

if (accessOrder)

makeTail((LinkedEntry<K, V>) e);

return e.value;

}

int hash = Collections.secondaryHash(key);

HashMapEntry<K, V>[] tab = table;

for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];

e != null; e = e.next) {

K eKey = e.key;

if (eKey == key || (e.hash == hash && key.equals(eKey))) {

if (accessOrder)

makeTail((LinkedEntry<K, V>) e);

return e.value;

}

}

return null;

}

其实仔细看 if (accessOrder) 的逻辑即可,如果 accessOrder=true 那么每次 get 都会执行 N 次 makeTail(LinkedEntry<K, V> e) 。

接下来看看:

3.5 LinkedHashMap.makeTail(LinkedEntry<K, V> e)

/**

  • Relinks the given entry to the tail of the list. Under access ordering,

  • this method is invoked whenever the value of a pre-existing entry is

  • read by Map.get or modified by Map.put.

*/

private void makeTail(LinkedEntry<K, V> e) {

// Unlink e

e.prv.nxt = e.nxt;

e.nxt.prv = e.prv;

// Relink e as tail

LinkedEntry<K, V> header = this.header;

LinkedEntry<K, V> oldTail = header.prv;

e.nxt = header;

e.prv = oldTail;

oldTail.nxt = header.prv = e;

modCount++;

}

// Unlink e

// Relink e as tail

LinkedHashMap 是双向循环链表,然后此次   LruCache.get -> LinkedHashMap.get   的数据就被放到最末尾了。

以上就是 LruCache 核心工作原理。

接下来介绍 LruCache 的容量溢出策略。

4.6 LruCache.put(K key, V value)

public final V put(K key, V value) {

synchronized (this) {

// 拿到键值对,计算出在容量中的相对长度,然后加上

size += safeSizeOf(key, value);

}

trimToSize(maxSize);

return previous;

}

记住几点:

  • 1.``put 开始的时候确实是把值放入 LinkedHashMap 了,不管超不超过你设定的缓存容量。

  • 2.然后根据 safeSizeOf 方法计算 此次添加数据的容量是多少,并且加到 size里 。

  • 3. 说到 safeSizeOf 就要讲到 sizeOf(K key, V value) 会计算出此次添加数据的大小 。

  • 4.直到 put 要结束时,进行了 trimToSize 才判断 size 是否 大于 maxSize 然后进行最近很少访问数据的移除。

4.7 LruCache.trimToSize(int maxSize)

public void trimToSize(int maxSize) {

/*

  • 这是一个死循环,

  • 1.只有 扩容 的情况下能立即跳出

  • 2.非扩容的情况下,map的数据会一个一个删除,直到map里没有值了,就会跳出

*/

while (true) {

K key;

V value;

synchronized (this) {

// 在重新调整容量大小前,本身容量就为空的话,会出异常的。

if (size < 0 || (map.isEmpty() && size != 0)) {

throw new IllegalStateException(

getClass().getName() + “.sizeOf() is reporting inconsistent results!”);

}

// 如果是 扩容 或者 map为空了,就会中断,因为扩容不会涉及到丢弃数据的情况

if (size <= maxSize || map.isEmpty()) {

break;

}

Map.Entry<K, V> toEvict = map.entrySet().iterator().next();

key = toEvict.getKey();

value = toEvict.getValue();

map.remove(key);

// 拿到键值对,计算出在容量中的相对长度,然后减去。

size -= safeSizeOf(key, value);

// 添加一次收回次数

evictionCount++;

}

/*

  • 将最后一次删除的最少访问数据回调出去

*/

entryRemoved(true, key, value, null);

}

}

总结

最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 诸多细节**,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

[外链图片转存中…(img-ShpffN30-1715250290599)]

[外链图片转存中…(img-avcbruw9-1715250290599)]

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值