<转载>Android性能优化之HashMap,ArrayMap和SparseArray

Android集合优化
本文探讨了Android中HashMap的替代方案,包括ArrayMap、SimpleArrayMap、SparseArray及其变种。通过对比不同集合类的内部实现,分析了它们在内存优化和性能提升方面的优势,特别强调了在Android开发中减少对象创建的重要性。
本篇博客来自于转载,打开原文地址已经失效,在此就不贴出原文地址了,如原作者看到请私信我可用地址,保护原创,人人有责。
 
Android开发者都知道Lint在我们使用HashMap的时候会给出警告——使用SparseArray会优化内存。这可是一件好事情。那现在我们有几个类要学习去使用。比如:ArrayMap和SimpleArrayMap,当然还有各种类型的SparseArray。这篇文章将讲解这些类及它们的原理。
先从如何使用它们开始吧。
 1   java.util.HashMap<String, String> hashMap = new java.util.HashMap<String, String>(16);
 2     hashMap.put("key", "value"); 
 3     hashMap.get("key"); 
 4     hashMap.entrySet().iterator(); 
 5     
 6     android.util.ArrayMap<String, String> arrayMap = new android.util.ArrayMap<String, String>(16);
 7     arrayMap.put("key", "value"); 
 8     arrayMap.get("key"); 
 9     arrayMap.entrySet().iterator(); 
10     
11     android.support.v4.util.ArrayMap<String, String> supportArrayMap = new android.support.v4.util.ArrayMap<String, String>(16); 
12     supportArrayMap.put("key", "value"); 
13     supportArrayMap.get("key"); 
14     supportArrayMap.entrySet().iterator(); 
15     
16     android.support.v4.util.SimpleArrayMap<String, String> simpleArrayMap = new android.support.v4.util.SimpleArrayMap<String, String>(16); 
17     simpleArrayMap.put("key", "value"); 
18     simpleArrayMap.get("key"); 
19     //simpleArrayMap.entrySet().iterator(); <- will not compile 
20     
21     android.util.SparseArray<String> sparseArray = new android.util.SparseArray<String>(16); 
22     sparseArray.put(10, "value"); 
23     sparseArray.get(10); 
24     
25     android.util.LongSparseArray<String> longSparseArray = new android.util.LongSparseArray<String>(16); 
26     ongSparseArray.put(10L, "value"); 
27     longSparseArray.get(10L); 
28     
29     android.util.SparseLongArray sparseLongArray = new android.util.SparseLongArray(16); 
30     sparseLongArray.put(10, 100L); 
31     sparseLongArray.get(10); 

接下我们一个一个的讨论。java中的集合基本都是基于数组。在我们了解这些替代类之前我们需要理解HashMap是怎么样工作的。

java.util.HashMap

关于HashMap请参照我另一篇博客,此处不再细说:Android版数据结构与算法(四):基于哈希表实现HashMap核心源码彻底分析

 

android.util.ArrayMap
ArrayMap 用了两个数组。在它内部用了Object[] mArray来存储Object,还用了int[] mHashes 来存储hashcode,当存储一对键值对的时候。
  • Key/Value会被自动装箱。
  • key会存储在mArray[]的下一个可用的位置。
  • 而value会存储在mArray[]中key的下一个位置。(key和value在mArray中交叉存储)
  • key的哈希值会被计算出来并存储在mHashed[]中。
    当查找一个key的时候:
  • 计算key的hashcode。
  • 在mHashes[]中对这个hashcode进行二分法查找。也就意味着时间复杂度增加到了O(logN)
  • 一旦我们得到了这个哈希值的位置index。我们就知道这个key是在mArray的2index的位置,而value则在2index+1的位置。
    这个ArrayMap还是没能解决自动装箱的问题。当put一对键值对进入的时候,它们只接受Object,但是我们相对于HashMap来说每一次put会少创建一个对象(HashMapEntry)。这是不是值得我们用O(1)的查找复杂度来换呢?对于大多数app应用来说是值得的。

android.support.v4.util.ArrayMap

android.util.ArrayMap只能在api不小于19(Kitkat)的平台才能使用。而Support library则支持在旧平台上提供相同的功能。

android.support.v4.util.SimpleArrayMap

在之前发布的代码片段中你可以看到,这个类没有entrySet()这个支持迭代的方法。如果你查看它的文档,你会发现很java标准集合的方法它都没有。那我们为什么要用它呢。让它失去与其它java容器的相互操作的特性来减小apk的大小。
这样的话, Proguard(代码优化和混沌工具:可能是你代码构建生成的一部分)可以帮你减少大多数没有使用的Collections API代码从而减小你的apk大小。它的内部工作和android.util.ArrayMap是一样的。
 
android.util.SparseArray
和ArrayMap一样,它里面也用了两个数组。一个int[] mKeys和Object[] mValues。从名字都可以看得出来一个用来存储key一个用来保存value的。
当保存一对键值对的时候:
  • key(不是它的hashcode)保存在mKeys[]的下一个可用的位置上。所以不会再对key自动装箱了。
  • value保存在mValues[]的下一个位置上,value还是要自动装箱的,如果它是基本类型。
    查找的时候:
  • 查找key还是用的二分法查找。也就是说它的时间复杂度还是O(logN)
  • 知道了key的index,也就可以用key的index来从mValues中检索出value。
    相较于HashMap,我们舍弃了Entry和Object类型的key,放弃了HashCode并依赖于二分法查找。在添加和删除操作的时候有更好的性能开销。
    KitKat以前的版本用android.support.v4.util.SparseArrayCompat   

android.util.LongSparseArray

SparseArray只接受int类型作为key,而LongSparseArray我们就可以用long作为key。实现原理和SparseArray一致。

android.util.SparseIntArray与android.util.SparseLongArray与android.util.SparseBooleanArray

对于key是int类型而value是int 或者long再或者是boolean,我们可以对应使用SparseIntArray,SparseLongArray ,SparseBooleanArray 。它们使用方式是和SparseArray一样的。它的好处是mValues[]是基本类型的数组。也就意味着无论是key还是value都不用装箱。并且相对于HashMap来说我们节约了3个对象的初始化(Entry,Key和Value),但是我们将查看复杂度从O(1)上升到了O(logN)

 

结语

使用SparseArray和ArrayMap肯定会减少对象创建的数目。当集合的的数目多达几百个的时候,性能差异也不会很明显(少于50%)。将ArrayMap和SparseArray迁移到新代码中是很有好处的。并且由于方法签名匹配,所以迁移也很容易。

注意:即使它们听起来像数组(Array),ArrayMap和SparseArray不能保证保留它们的插入顺序,在迭代的时候应该注意。

转载于:https://www.cnblogs.com/leipDao/p/9996205.html

<think>好的,用户现在想理解代码中SparseArray的用途以及mDisplayPowerControllers变量的定义作用。他们之前已经询问过关于synchronized、BinderDisplayPowerController的内容,现在结合新的引用资料,需要详细解释SparseArray在Android中的使用,以及mDisplayPowerControllers作为SparseArray存储DisplayPowerController对象的意义。 首先,我需要回顾用户提供的引用内容,这些引用主要介绍了SparseArray的特点、构造方法、HashMap的区别等。用户提到的引用1到5都强调了SparseArray在节省内存方面的优势,特别是避免自动装箱数组结构。用户之前的问题涉及多线程同步、Binder机制显示电源管理,现在需要将这些SparseArray的应用联系起来。 接下来,我需要明确用户的问题点:SparseArray的使用方法,以及mDisplayPowerControllers作为SparseArray存储DisplayPowerController的意义。用户可能是在阅读Android系统源码时遇到了这个变量,希望了解其设计背后的原因。 首先,解释SparseArray的基本使用,包括初始化、增删改查操作,以及适用的场景。需要对比HashMap,突出SparseArray在内存性能上的优势,特别是在键为整型时的效率。然后,结合用户之前的上下文,说明在DisplayPowerController的管理中使用SparseArray的合理性,比如可能根据displayId(整数)来索引不同的控制器,这样SparseArray的高效查找低内存占用就显得尤为重要。 同时,需要注意用户提供的代码片段中有mDisplayPowerControllers.get(displayId),这说明该SparseArray是以displayId为键存储DisplayPowerController实例。需要解释这种设计的优势,例如支持多显示屏环境下的高效管理,快速通过displayId获取对应的控制器实例,同时节省内存资源。 此外,用户可能想知道为什么选择SparseArray而不是其他数据结构,比如ArrayMap或者传统的HashMap。根据引用资料,SparseArray在键为int时更为高效,避免了自动装箱,适合键分布稀疏的情况。而DisplayPowerController可能涉及多个显示屏,displayId可能是不连续或稀疏的整数,因此SparseArray更适合这种场景。 最后,总结SparseArray在系统级代码中的意义,强调其在高性能高内存效率要求下的优势,以及mDisplayPowerControllers使用SparseArray的具体好处,如快速访问、低内存开销线程安全支持等。 需要确回答结构清晰,分点说明,并结合用户提供的引用内容之前的讨论,持引用标记正确,例如[^1][^3][^4]等。同时,生成的相关问题要围绕SparseArray的应用场景、其他数据结构的对比,以及在系统设计中的具体实践展开,帮助用户进一步理解。</think>### SparseArray 在 Android 中的用途使用方法 `SparseArray` 是 Android 专门为 `int` 类型键值设计的优化数据结构,主要用于替代 `HashMap<Integer, Object>`,在内存效率性能上具有显著优势[^1][^3][^4]。 #### 核心特点: 1. **内存优化**: - 使用两个平行数组(`int[]` 存储键,`Object[]` 存储值),避免 `HashMap` 的自动装箱(`Integer` 对象)开销[^1][^5]。 - 适用于键分布稀疏的场景(如不连续的整数键)。 2. **性能优势**: - **查找**:通过二分查找定位键,时间复杂度为 $O(\log n)$,适合小规模数据[^3][^4]。 - **插入/删除**:直接操作数组,无需哈希冲突处理,但高频增删时效率可能低于 `HashMap`[^5]。 #### 典型使用场景: ```java // 初始化 SparseArray<String> sparseArray = new SparseArray<>(); // 增/改 sparseArray.put(1001, "DisplayController_A"); sparseArray.put(1002, "DisplayController_B"); // 查 String controller = sparseArray.get(1001); // 删 sparseArray.delete(1002); // 遍历(需手动处理索引) for (int i = 0; i < sparseArray.size(); i++) { int key = sparseArray.keyAt(i); String value = sparseArray.valueAt(i); } ``` --- ### `mDisplayPowerControllers` 变量的定义作用 在 Android 显示电源管理模块(如 `DisplayManagerService`)中,`mDisplayPowerControllers` 通常定义为: ```java private final SparseArray<DisplayPowerController> mDisplayPowerControllers = new SparseArray<>(); ``` #### 设计意义: 1. **多显示屏管理**: - Android 设备可能支持多块物理/虚拟屏幕(如主屏、副屏、投屏),每个显示屏通过唯一的 `displayId`(整数)标识。 - `SparseArray` 以 `displayId` 为键,快速关联对应的 `DisplayPowerController` 实例,例如: ```java // 根据 displayId 获取控制器 DisplayPowerController controller = mDisplayPowerControllers.get(displayId); controller.setTemporaryBrightness(brightness); ``` 2. **高效内存利用**: - `displayId` 通常是系统分配的稀疏整数(如 0 主屏,1 虚拟屏),`SparseArray` 的双数组结构比 `HashMap` 更节省内存[^3][^5]。 - 避免 `HashMap` 因哈希冲突导致的链表或红黑树开销。 3. **线程安全支持**: - 结合 `synchronized` 同步块(如用户提供的代码片段),确多线程环境下对 `SparseArray` 的操作安全[^1]。 --- ### 对比:`SparseArray` vs. `HashMap<Integer, Object>` | 特性 | SparseArray | HashMap<Integer, Object> | |---------------------|----------------------------|--------------------------------| | **键类型** | `int` | `Integer`(自动装箱) | | **内存占用** | 更低(无对象头开销)[^1][^3] | 更高(哈希桶 + 链表/红黑树) | | **查找效率** | $O(\log n)$(二分查找) | $O(1)$(理想哈希) | | **适用场景** | 小规模数据、键稀疏 | 大规模数据、高频增删 | --- ### 系统级设计启示 在 Android 框架中,类似 `mDisplayPowerControllers` 的 `SparseArray` 使用体现了以下设计原则: 1. **资源敏感优化**:针对移动端内存限制,优先选择轻量级数据结构[^5]。 2. **类型特化**:直接使用 `int` 键避免泛型擦除装箱成本[^4]。 3. **可扩展性**:通过 `SparseArray` 支持动态增删显示屏控制器,适应多屏设备需求。 --- ### 相关问题 1. 在 Android 中如何选择 `SparseArray` `ArrayMap`? 2. `SparseArray` 的二分查找实现如何优化性能? 3. 多显示屏场景下,`DisplayPowerController` 如何协调亮度策略?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值