最后
感谢您的阅读,在文末给大家准备一个福利。本人从事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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!