其他
Google 为何设计了如此难用的 ArrayMap
本文作者
作者:却把清梅嗅
链接:
https://juejin.cn/post/7405842860035424266
本文由作者授权发布。
天下苦 ArrayMap 久矣。
ArrayMap 初衷是为了解决什么问题? ArrayMap 的设计缺陷是什么,如何正确使用? ArrayMap 和 HashMap 的对比,如何取舍?
2.1 减少pc成本
本文不会对 ArrayMap 进行源码级的讲解,读者仅需梳理思路即可。
public final class ArrayMap<K, V> implements Map<K, V> {
int[] mHashes;
Object[] mArray;
int mSize;
}
简单介绍最重要的2个数组成员:
mHashes: 整数数组,保存 key 的 hashcode mArray: 对象数组,顺序保存 key-value
2.2 减少内存占用
2.3 较高的读写效率
若找到存在相同键的位置,那就直接覆盖值。 若未找到,则在插入键值对时动态调整数组并保持 有序性。
2.4 进一步优化
public final class ArrayMap<K, V> implements Map<K, V> {
static Object[] mBaseCache;
static Object[] mTwiceBaseCache;
}
简而言之,其内部使用一个 静态缓存池 存储已被回收但还可重用的内部存储结构。这包括一组预先定义的对象缓存队列,用来存储不同大小的数组。
private void allocArrays(int size) {
if (size == BASE_SIZE) {
synchronized (sBaseCacheLock) {
if (mBaseCache != null) {
// 1.从静态缓存池中取出
final Object[] array = mBaseCache;
// 2.复用之
mArray = array;
mBaseCache = (Object[]) array[0]; // 【重要】3.3 会讲
mHashes = (int[]) array[1];
array[0] = array[1] = null;
// ....
}
}
}
// 3. 无可用缓存,再new
mKeys = new Object[size];
mValues = new Object[size];
}
3.1 线上的奇怪崩溃
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]
at androidx.collection.SimpleArrayMap.allocArrays(SimpleArrayMap.java:184)
at androidx.collection.SimpleArrayMap.put(SimpleArrayMap.java:458)
at com.bumptech.glide.util.CachedHashCodeArrayMap.put(CachedHashCodeArrayMap.java:34)
at com.bumptech.glide.request.BaseRequestOptions.transform(BaseRequestOptions.java:1017)
at com.bumptech.glide.request.BaseRequestOptions.transform(BaseRequestOptions.java:971)
令人摸不到头脑的日志,来看崩溃的业务代码:
GlideApp.with(context).load(path).transform(xxx).into(imageView);
有点更蒙圈了,自测几乎无法复现,这到底什么情况?
3.2 超级不安全
// SimpleArrayMap.java
private void allocArrays(int size) {
// 省略其它
// 1.从静态缓存池中取出
final Object[] array = mBaseCache;
// 2.复用之
mArray = array;
// *************************
// 3.此时,其它线程进行了写操作,如 mArray.put(XXX),即 mBaseCache 缓存池受到了污染.
// *************************
mBaseCache = (Object[]) array[0]; // 4.此时读取缓存池数据,脏数据导致类型异常,app崩溃
// 省略其它
}
需要强调的是,当 mBaseCache 缓存池受到污染后,可能并不会引起崩溃,而是把隐患埋了下来,当下一次,在其它业务场景下,通过 new ArrayMap() 创建对象并申请内存时,缓存池复用才会抛出异常。
3.3 使用建议
3.4 躲都躲不掉
感谢评论区 @Goooler 的提醒(评论误删了..),1.4.3 版本 SimpleArrayMap 使用 kotlin 进行了重写,源码解决了静态缓存池所带来的线程不安全的问题,大家可以通过手动指定 collcetion 库的版本的方式规避。
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!