Android 性能为王期间SparseArray和HashMap一争高下

知者何南  金牌会员 | 2024-6-27 02:32:25 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 558|帖子 558|积分 1674

SparseArray 是 Android 中一种高效的数据结构,用于将整数键映射到对象。它与 HashMap 雷同,但为了节省内存,使用两个并行数组来存储键和值,并采用二分搜索进行查找。以下是对 SparseArray 源码的详细分析。
一、SparseArray 源码分析

1. 类定义和构造函数

SparseArray 是一个泛型类,继续自 Object。
  1. public class SparseArray<E> implements Cloneable {
  2.     private static final Object DELETED = new Object();
  3.     private boolean mGarbage = false;
  4.     private int[] mKeys;
  5.     private Object[] mValues;
  6.     private int mSize;
  7.     public SparseArray() {
  8.         this(10);  // 默认初始容量为10
  9.     }
  10.     public SparseArray(int initialCapacity) {
  11.         if (initialCapacity == 0) {
  12.             mKeys = EmptyArray.INT;
  13.             mValues = EmptyArray.OBJECT;
  14.         } else {
  15.             mKeys = new int[initialCapacity];
  16.             mValues = new Object[initialCapacity];
  17.         }
  18.         mSize = 0;
  19.     }
  20. }
复制代码
2. 基本方法

2.1 put(int key, E value)

将键值对插入 SparseArray 中。
  1. public void put(int key, E value) {
  2.     int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
  3.     if (i >= 0) {
  4.         mValues[i] = value;
  5.     } else {
  6.         i = ~i;
  7.         if (i < mSize && mValues[i] == DELETED) {
  8.             mKeys[i] = key;
  9.             mValues[i] = value;
  10.             return;
  11.         }
  12.         if (mGarbage && mSize >= mKeys.length) {
  13.             gc();
  14.             i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
  15.         }
  16.         mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
  17.         mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
  18.         mSize++;
  19.     }
  20. }
复制代码
2.2 get(int key)

通过键获取值,如果不存在则返回默认值 null。
  1. public E get(int key) {
  2.     return get(key, null);
  3. }
  4. public E get(int key, E valueIfKeyNotFound) {
  5.     int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
  6.     if (i < 0 || mValues[i] == DELETED) {
  7.         return valueIfKeyNotFound;
  8.     } else {
  9.         return (E) mValues[i];
  10.     }
  11. }
复制代码
2.3 delete(int key)

删除键值对。
  1. public void delete(int key) {
  2.     int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
  3.     if (i >= 0) {
  4.         if (mValues[i] != DELETED) {
  5.             mValues[i] = DELETED;
  6.             mGarbage = true;
  7.         }
  8.     }
  9. }
复制代码
2.4 removeAt(int index)

删除指定索引处的键值对。
  1. public void removeAt(int index) {
  2.     if (mValues[index] != DELETED) {
  3.         mValues[index] = DELETED;
  4.         mGarbage = true;
  5.     }
  6. }
复制代码
2.5 gc()

垃圾回收,清理被标记删除的元素。
  1. private void gc() {
  2.     int n = mSize;
  3.     int o = 0;
  4.     int[] keys = mKeys;
  5.     Object[] values = mValues;
  6.     for (int i = 0; i < n; i++) {
  7.         Object val = values[i];
  8.         if (val != DELETED) {
  9.             if (i != o) {
  10.                 keys[o] = keys[i];
  11.                 values[o] = val;
  12.                 values[i] = null;
  13.             }
  14.             o++;
  15.         }
  16.     }
  17.     mGarbage = false;
  18.     mSize = o;
  19. }
复制代码
2.6 size()

返回键值对的数目。
  1. public int size() {
  2.     if (mGarbage) {
  3.         gc();
  4.     }
  5.     return mSize;
  6. }
复制代码
2.7 keyAt(int index) 和 valueAt(int index)

通过索引获取键或值。
  1. public int keyAt(int index) {
  2.     if (mGarbage) {
  3.         gc();
  4.     }
  5.     return mKeys[index];
  6. }
  7. public E valueAt(int index) {
  8.     if (mGarbage) {
  9.         gc();
  10.     }
  11.     return (E) mValues[index];
  12. }
复制代码
3. 辅助方法

3.1 binarySearch()

二分搜索,用于在有序数组中查找元素。
  1. public static int binarySearch(int[] array, int size, int value) {
  2.     int lo = 0;
  3.     int hi = size - 1;
  4.     while (lo <= hi) {
  5.         final int mid = (lo + hi) >>> 1;
  6.         final int midVal = array[mid];
  7.         if (midVal < value) {
  8.             lo = mid + 1;
  9.         } else if (midVal > value) {
  10.             hi = mid - 1;
  11.         } else {
  12.             return mid; // value found
  13.         }
  14.     }
  15.     return ~lo;  // value not present
  16. }
复制代码
二、使用示例

以下是SparseArray的简单使用示例:
  1. SparseArray<String> sparseArray = new SparseArray<>();
  2. sparseArray.put(1, "One");
  3. sparseArray.put(2, "Two");
  4. sparseArray.put(3, "Three");
  5. // 获取值
  6. String value = sparseArray.get(2); // "Two"
  7. // 删除值
  8. sparseArray.delete(3);
  9. // 获取键和值
  10. for (int i = 0; i < sparseArray.size(); i++) {
  11.     int key = sparseArray.keyAt(i);
  12.     String val = sparseArray.valueAt(i);
  13.     Log.d("SparseArray", "Key: " + key + ", Value: " + val);
  14. }
复制代码
通过这种方式,我们可以高效地管理键为整数的键值对,特殊适用于性能敏感的应用场景。
继续深入分析SparseArray的实现细节,并探讨其优缺点和使用场景。
三、详细实现分析

3.1 ContainerHelpers 类

ContainerHelpers 提供了 SparseArray 使用的二分搜索功能。
  1. public class ContainerHelpers {
  2.     public static int binarySearch(int[] array, int size, int value) {
  3.         int lo = 0;
  4.         int hi = size - 1;
  5.         while (lo <= hi) {
  6.             final int mid = (lo + hi) >>> 1;
  7.             final int midVal = array[mid];
  8.             if (midVal < value) {
  9.                 lo = mid + 1;
  10.             } else if (midVal > value) {
  11.                 hi = mid - 1;
  12.             } else {
  13.                 return mid; // value found
  14.             }
  15.         }
  16.         return ~lo;  // value not present
  17.     }
  18. }
复制代码
该方法通过二分查找在一个有序整数数组中定位特定值的位置。如果找到匹配值,则返回其索引;否则返回插入点的反码(即 ~lo)。
3.2 GrowingArrayUtils 类

GrowingArrayUtils 用于在数组中插入元素并自动扩展数组容量。
  1. public class GrowingArrayUtils {
  2.     public static int[] insert(int[] array, int currentSize, int index, int element) {
  3.         if (currentSize + 1 > array.length) {
  4.             int[] newArray = new int[growSize(currentSize)];
  5.             System.arraycopy(array, 0, newArray, 0, index);
  6.             newArray[index] = element;
  7.             System.arraycopy(array, index, newArray, index + 1, currentSize - index);
  8.             return newArray;
  9.         } else {
  10.             System.arraycopy(array, index, array, index + 1, currentSize - index);
  11.             array[index] = element;
  12.             return array;
  13.         }
  14.     }
  15.     public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
  16.         if (currentSize + 1 > array.length) {
  17.             @SuppressWarnings("unchecked")
  18.             T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(), growSize(currentSize));
  19.             System.arraycopy(array, 0, newArray, 0, index);
  20.             newArray[index] = element;
  21.             System.arraycopy(array, index, newArray, index + 1, currentSize - index);
  22.             return newArray;
  23.         } else {
  24.             System.arraycopy(array, index, array, index + 1, currentSize - index);
  25.             array[index] = element;
  26.             return array;
  27.         }
  28.     }
  29.     private static int growSize(int currentSize) {
  30.         return currentSize <= 4 ? 8 : currentSize * 2;
  31.     }
  32. }
复制代码
该类提供了向数组中插入元素的方法,如果数组已满,则会扩展数组容量。growSize 方法根据当前大小决定扩展大小。
四、优缺点

4.1 优点


  • 内存效率高:SparseArray 使用并行数组,避免了 HashMap 中对象封装导致的内存开销,特殊适合键是整数的环境。
  • 高效查找:通过二分查找在键数组中定位元素,查找时间复杂度为 O(log N)。
  • 自动扩展:GrowingArrayUtils 确保数组在需要时自动扩展,镌汰手动管理数组大小的贫苦。
  • 避免自动装箱:与 HashMap<Integer, Object> 差别,SparseArray 直接使用 int 类型键,避免了自动装箱的开销。
4.2 缺点


  • 不适合频仍删除操纵:删除操纵只是将值标记为 “已删除”,需要额外的垃圾回收步骤,这大概影响性能。
  • 键必须是整数:只能用于整数键的环境,不敷通用。
  • 固定容量扩展:数组扩展是按固定策略进行的(当前大小的倍数扩展),在某些极端环境下大概导致不必要的内存浪费。
五、使用场景

5.1 适用场景


  • 大量键值对:适用于需要存储大量键值对且键为整数的场景,如缓存、映射关系等。
  • 高性能要求:适合内存敏感的应用,如低端装备上的应用、实时应用等。
  • 稀疏数据集:特殊适用于键值对稀疏分布的场景。
5.2 不适用场景


  • 频仍插入删除:如果应用需要频仍插入和删除操纵,SparseArray 的性能大概不如 HashMap。
  • 非整数键:如果键不是整数,SparseArray 无法使用。
六、实际使用示例

下面是一个实际应用场景中的示例,用于存储和查找用户会话数据:
  1. public class SessionManager {
  2.     private SparseArray<Session> sessionSparseArray;
  3.     public SessionManager() {
  4.         sessionSparseArray = new SparseArray<>();
  5.     }
  6.     public void addSession(int sessionId, Session session) {
  7.         sessionSparseArray.put(sessionId, session);
  8.     }
  9.     public Session getSession(int sessionId) {
  10.         return sessionSparseArray.get(sessionId);
  11.     }
  12.     public void removeSession(int sessionId) {
  13.         sessionSparseArray.delete(sessionId);
  14.     }
  15.     public int getSessionCount() {
  16.         return sessionSparseArray.size();
  17.     }
  18.     // 清理被标记删除的会话
  19.     public void cleanUpSessions() {
  20.         for (int i = 0; i < sessionSparseArray.size(); i++) {
  21.             int key = sessionSparseArray.keyAt(i);
  22.             Session session = sessionSparseArray.get(key);
  23.             if (session.isExpired()) {
  24.                 sessionSparseArray.removeAt(i);
  25.             }
  26.         }
  27.     }
  28. }
  29. class Session {
  30.     private long creationTime;
  31.     private long expiryTime;
  32.     public Session(long creationTime, long expiryTime) {
  33.         this.creationTime = creationTime;
  34.         this.expiryTime = expiryTime;
  35.     }
  36.     public boolean isExpired() {
  37.         return System.currentTimeMillis() > expiryTime;
  38.     }
  39. }
复制代码
在这个示例中,SessionManager 使用 SparseArray 存储和管理用户会话。通过addSession、getSession、removeSession等方法,可以高效地管剖析话数据。cleanUpSessions 方法演示了怎样清理过期会话,同时展示了删除标记和垃圾回收机制。
七、总结

SparseArray 是 Android 提供的一个高效数据结构,用于整数键值对的存储和查找。它通过优化内存使用和查找性能,特殊适合在性能敏感和内存有限的应用中使用。通过理解实在现原理和优缺点,可以在恰当的场景中充分使用其上风。
SparseArray 是一种优化的稀疏数组,适用于键为整数的场景。它的实现通过两个并行数组和二分搜索来进步查找和存储的效率,避免了使用HashMap大概带来的内存开销。


  • 存储:使用两个并行数组分别存储键和值。
  • 查找:通过二分搜索快速定位键的位置。
  • 垃圾回收:耽误删除机制,通过标记删除和垃圾回收镌汰数组重新分配次数。
  • 性能优化:通过ViewHolder模式和镌汰对象分配,SparseArray 在大量数据操纵时性能表现良好。
欢迎点赞|关注|收藏|品评,您的肯定是我创作的动力


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

知者何南

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表