Android 性能为王时代SparseArray和HashMap一争高下

news2024/12/28 21:07:12

文章目录

      • 一、`SparseArray` 源码分析
        • 1. **类定义和构造函数**
        • 2. **基本方法**
          • 2.1 `put(int key, E value)`
          • 2.2 `get(int key)`
          • 2.3 `delete(int key)`
          • 2.4 `removeAt(int index)`
          • 2.5 `gc()`
          • 2.6 `size()`
          • 2.7 `keyAt(int index)` 和 `valueAt(int index)`
        • 3. **辅助方法**
          • 3.1 `binarySearch()`
      • 二、使用示例
      • 三、详细实现分析
        • 3.1 `ContainerHelpers` 类
        • 3.2 `GrowingArrayUtils` 类
      • 四、优缺点
        • 4.1 优点
        • 4.2 缺点
      • 五、使用场景
        • 5.1 适用场景
        • 5.2 不适用场景
      • 六、实际使用示例
      • 七、总结

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

一、SparseArray 源码分析

1. 类定义和构造函数

SparseArray 是一个泛型类,继承自 Object

public class SparseArray<E> implements Cloneable {
    private static final Object DELETED = new Object();
    private boolean mGarbage = false;
    private int[] mKeys;
    private Object[] mValues;
    private int mSize;

    public SparseArray() {
        this(10);  // 默认初始容量为10
    }

    public SparseArray(int initialCapacity) {
        if (initialCapacity == 0) {
            mKeys = EmptyArray.INT;
            mValues = EmptyArray.OBJECT;
        } else {
            mKeys = new int[initialCapacity];
            mValues = new Object[initialCapacity];
        }
        mSize = 0;
    }
}
2. 基本方法
2.1 put(int key, E value)

将键值对插入 SparseArray 中。

public void put(int key, E value) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

    if (i >= 0) {
        mValues[i] = value;
    } else {
        i = ~i;

        if (i < mSize && mValues[i] == DELETED) {
            mKeys[i] = key;
            mValues[i] = value;
            return;
        }

        if (mGarbage && mSize >= mKeys.length) {
            gc();

            i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
        }

        mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
        mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
        mSize++;
    }
}
2.2 get(int key)

通过键获取值,如果不存在则返回默认值 null

public E get(int key) {
    return get(key, null);
}

public E get(int key, E valueIfKeyNotFound) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

    if (i < 0 || mValues[i] == DELETED) {
        return valueIfKeyNotFound;
    } else {
        return (E) mValues[i];
    }
}
2.3 delete(int key)

删除键值对。

public void delete(int key) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

    if (i >= 0) {
        if (mValues[i] != DELETED) {
            mValues[i] = DELETED;
            mGarbage = true;
        }
    }
}
2.4 removeAt(int index)

删除指定索引处的键值对。

public void removeAt(int index) {
    if (mValues[index] != DELETED) {
        mValues[index] = DELETED;
        mGarbage = true;
    }
}
2.5 gc()

垃圾回收,清理被标记删除的元素。

private void gc() {
    int n = mSize;
    int o = 0;
    int[] keys = mKeys;
    Object[] values = mValues;

    for (int i = 0; i < n; i++) {
        Object val = values[i];

        if (val != DELETED) {
            if (i != o) {
                keys[o] = keys[i];
                values[o] = val;
                values[i] = null;
            }

            o++;
        }
    }

    mGarbage = false;
    mSize = o;
}
2.6 size()

返回键值对的数量。

public int size() {
    if (mGarbage) {
        gc();
    }
    return mSize;
}
2.7 keyAt(int index)valueAt(int index)

通过索引获取键或值。

public int keyAt(int index) {
    if (mGarbage) {
        gc();
    }
    return mKeys[index];
}

public E valueAt(int index) {
    if (mGarbage) {
        gc();
    }
    return (E) mValues[index];
}
3. 辅助方法
3.1 binarySearch()

二分搜索,用于在有序数组中查找元素。

public static int binarySearch(int[] array, int size, int value) {
    int lo = 0;
    int hi = size - 1;

    while (lo <= hi) {
        final int mid = (lo + hi) >>> 1;
        final int midVal = array[mid];

        if (midVal < value) {
            lo = mid + 1;
        } else if (midVal > value) {
            hi = mid - 1;
        } else {
            return mid; // value found
        }
    }
    return ~lo;  // value not present
}

二、使用示例

以下是SparseArray的简单使用示例:

SparseArray<String> sparseArray = new SparseArray<>();
sparseArray.put(1, "One");
sparseArray.put(2, "Two");
sparseArray.put(3, "Three");

// 获取值
String value = sparseArray.get(2); // "Two"

// 删除值
sparseArray.delete(3);

// 获取键和值
for (int i = 0; i < sparseArray.size(); i++) {
    int key = sparseArray.keyAt(i);
    String val = sparseArray.valueAt(i);
    Log.d("SparseArray", "Key: " + key + ", Value: " + val);
}

通过这种方式,我们可以高效地管理键为整数的键值对,特别适用于性能敏感的应用场景。

继续深入分析SparseArray的实现细节,并探讨其优缺点和使用场景。

三、详细实现分析

3.1 ContainerHelpers

ContainerHelpers 提供了 SparseArray 使用的二分搜索功能。

public class ContainerHelpers {
    public static int binarySearch(int[] array, int size, int value) {
        int lo = 0;
        int hi = size - 1;

        while (lo <= hi) {
            final int mid = (lo + hi) >>> 1;
            final int midVal = array[mid];

            if (midVal < value) {
                lo = mid + 1;
            } else if (midVal > value) {
                hi = mid - 1;
            } else {
                return mid; // value found
            }
        }
        return ~lo;  // value not present
    }
}

该方法通过二分查找在一个有序整数数组中定位特定值的位置。如果找到匹配值,则返回其索引;否则返回插入点的反码(即 ~lo)。

3.2 GrowingArrayUtils

GrowingArrayUtils 用于在数组中插入元素并自动扩展数组容量。

public class GrowingArrayUtils {
    public static int[] insert(int[] array, int currentSize, int index, int element) {
        if (currentSize + 1 > array.length) {
            int[] newArray = new int[growSize(currentSize)];
            System.arraycopy(array, 0, newArray, 0, index);
            newArray[index] = element;
            System.arraycopy(array, index, newArray, index + 1, currentSize - index);
            return newArray;
        } else {
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        }
    }

    public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
        if (currentSize + 1 > array.length) {
            @SuppressWarnings("unchecked")
            T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(), growSize(currentSize));
            System.arraycopy(array, 0, newArray, 0, index);
            newArray[index] = element;
            System.arraycopy(array, index, newArray, index + 1, currentSize - index);
            return newArray;
        } else {
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        }
    }

    private static int growSize(int currentSize) {
        return currentSize <= 4 ? 8 : currentSize * 2;
    }
}

该类提供了向数组中插入元素的方法,如果数组已满,则会扩展数组容量。growSize 方法根据当前大小决定扩展大小。

四、优缺点

4.1 优点
  1. 内存效率高SparseArray 使用并行数组,避免了 HashMap 中对象封装导致的内存开销,特别适合键是整数的情况。
  2. 高效查找:通过二分查找在键数组中定位元素,查找时间复杂度为 O(log N)。
  3. 自动扩展GrowingArrayUtils 确保数组在需要时自动扩展,减少手动管理数组大小的麻烦。
  4. 避免自动装箱:与 HashMap<Integer, Object> 不同,SparseArray 直接使用 int 类型键,避免了自动装箱的开销。
4.2 缺点
  1. 不适合频繁删除操作:删除操作只是将值标记为 “已删除”,需要额外的垃圾回收步骤,这可能影响性能。
  2. 键必须是整数:只能用于整数键的情况,不够通用。
  3. 固定容量扩展:数组扩展是按固定策略进行的(当前大小的倍数扩展),在某些极端情况下可能导致不必要的内存浪费。

五、使用场景

5.1 适用场景
  1. 大量键值对:适用于需要存储大量键值对且键为整数的场景,如缓存、映射关系等。
  2. 高性能要求:适合内存敏感的应用,如低端设备上的应用、实时应用等。
  3. 稀疏数据集:特别适用于键值对稀疏分布的场景。
5.2 不适用场景
  1. 频繁插入删除:如果应用需要频繁插入和删除操作,SparseArray 的性能可能不如 HashMap
  2. 非整数键:如果键不是整数,SparseArray 无法使用。

六、实际使用示例

下面是一个实际应用场景中的示例,用于存储和查找用户会话数据:

public class SessionManager {
    private SparseArray<Session> sessionSparseArray;

    public SessionManager() {
        sessionSparseArray = new SparseArray<>();
    }

    public void addSession(int sessionId, Session session) {
        sessionSparseArray.put(sessionId, session);
    }

    public Session getSession(int sessionId) {
        return sessionSparseArray.get(sessionId);
    }

    public void removeSession(int sessionId) {
        sessionSparseArray.delete(sessionId);
    }

    public int getSessionCount() {
        return sessionSparseArray.size();
    }

    // 清理被标记删除的会话
    public void cleanUpSessions() {
        for (int i = 0; i < sessionSparseArray.size(); i++) {
            int key = sessionSparseArray.keyAt(i);
            Session session = sessionSparseArray.get(key);
            if (session.isExpired()) {
                sessionSparseArray.removeAt(i);
            }
        }
    }
}

class Session {
    private long creationTime;
    private long expiryTime;

    public Session(long creationTime, long expiryTime) {
        this.creationTime = creationTime;
        this.expiryTime = expiryTime;
    }

    public boolean isExpired() {
        return System.currentTimeMillis() > expiryTime;
    }
}

在这个示例中,SessionManager 使用 SparseArray 存储和管理用户会话。通过addSessiongetSessionremoveSession等方法,可以高效地管理会话数据。cleanUpSessions 方法演示了如何清理过期会话,同时展示了删除标记和垃圾回收机制。

七、总结

SparseArray 是 Android 提供的一个高效数据结构,用于整数键值对的存储和查找。它通过优化内存使用和查找性能,特别适合在性能敏感和内存有限的应用中使用。通过理解其实现原理和优缺点,可以在适当的场景中充分利用其优势。

SparseArray 是一种优化的稀疏数组,适用于键为整数的场景。它的实现通过两个并行数组和二分搜索来提高查找和存储的效率,避免了使用HashMap可能带来的内存开销。

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1687180.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Axure软件安装教程

链接&#xff1a;https://pan.baidu.com/s/1fHrSrZ7PIeDZZpn6QyJ6jQ?pwdb4mv 提取码&#xff1a;b4mv 安装完后点击Finish 名字随便起 关闭Axure 复制到安装目录下 最后成果

SQL学习小记(一)

SQL学习小记&#xff08;一&#xff09; 1. 存储过程&存储函数1.1. 存储过程1.2. 存储函数 2. DEFINER3. INSERT INTO&#xff08;插入新记录&#xff09;4. REPLACE()…AS…5. SUM()函数6. CASE WHEN7. STR_TO_DATE日期时间处理函数8. SUBSTRING函数9. dateFormat函数10. …

Python数据可视化(六)

实现事件处理效果 我们借助 matplotlib 可以实现事件处理效果&#xff0c;例如&#xff0c;单击关闭画布会出现画布被关闭的文本提 示&#xff0c;在画布上的图形界面任意位置单击可以获得放大后的此处图形界面等。下面&#xff0c;我们就挑选一些 典型的事件处理案例来讲解实现…

7 Series FPGAs Integrated Block for PCI Express IP核 Advanced模式配置详解(三)

1 TL Settings Transaction Layer (TL)设置只在Advanced模式下有效。 Endpoint: Unlock and PME_Turn_Off Messages: 与端点的电源管理相关&#xff0c;允许发送解锁和电源管理事件关闭消息。 Root Port: Error Messages: Error Correctable&#xff08;错误可纠正&#xff09…

探数API统计分享-1949年-2021年中国历年夏粮产量统计报告

​​​​​​​​中国历年夏粮产量​&#xff0c;为1949年到2021年我国每年的夏粮产量数据。2021年&#xff0c;我国夏粮产量为14596万吨&#xff0c;比上年增长2.2%。 数据统计单位为&#xff1a;万吨 。 我国夏粮产量有多少&#xff1f; 2021年&#xff0c;我国夏粮产量为1…

【小白向】MAC端VSCode C++环境配置(超干货、超详细)

提示&#xff1a;使用环境为 MAC&#xff08;M2&#xff09; 其实 VSCode 很早就下载好了&#xff0c;但是因为在配置过程中总是遇到很多坑&#xff0c;搁置了很久&#xff0c;回头捡起遇到报 Error 还是两眼抓瞎&#xff0c;到处翻 blog。为了减少以后的遇坑可能性&#xff0c…

uniapp开发安卓app高德地图

uniapp开发安卓app高德地图 一、高德创建key二、uniapp配置三、uniapp打包证书获取 一、高德创建key 优先去高德地图开发者平台去创建关联项目 高德开发者平台 打开我的应用&#xff0c;添加key 进行高德key的配置 其中key建议设置成app的名称&#xff0c;便于区分SHA1获取可以…

【ChatGPT】 Microsoft Edge 浏览器扩展使用 GPT

【ChatGPT】添加 Microsoft Edge 浏览器插件免费使用 GPT 文章目录 准备工作添加扩展注意事项 使用 ChatGPT 可以更高效的搜索到想要的内容&#xff0c;有效节约在搜索引擎中排查正确信息的时间。 准备工作 准备一台可上网的电脑电脑上安装有 Windows 自带的 Microsoft Edge …

剪画小程序:3个方法:告诉你如何将普通的照片转换成动漫二次元风格!

Hello&#xff01;亲爱的小伙伴们&#xff01; 你是否还在纠结于自己的自拍太普通&#xff0c;每次分享到社交账号上都觉得平平无奇&#xff0c;引不起波澜&#xff1f; 假如&#xff0c;你和朋友们一起出去玩&#xff0c;大家都开心地拍着自拍。你看着自己的照片&#xff0c…

【ONE·MySQL || 视图和用户管理】

总言 主要内容&#xff1a;介绍MySQL中视图和用户管理。             文章目录 总言1、视图1.1、基本介绍1.2、相关操作1.2.1、创建及查看视图1.2.2、修改视图1.2.3、更新视图1.2.4、删除视图 2、用户管理2.1、用户管理2.1.1、基本介绍2.1.2、使用用户登录MySQL服务器2…

MyBatis中Where标签:揭秘高效SQL构建的秘密

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 理解Where标签的基础概念 在MyBatis中&#xff0c;<where>标签是用于构建SQL查询语句中的一个非常重要的元素。它允许你在一个动态的SQL语句中添加WHERE子句&#xff0c;而不需要担心SQL语法错误或额外的逗号…

【Linux】之【Get√】查看linux CPU 架构 ---- amd64、x86、x86_64、arm64

关于Linux软件下载时&#xff1a;amd64、x86、x86_64、arm64 的说明 archuanme -alscpu

MySQL中视图是什么,有什么作用

目录 一、视图的简介 1.1 什么是视图&#xff1f; 1.2 为什么使用视图&#xff1f; 1.3 视图有哪些规则与限制&#xff1f; 1.4 视图能否更新&#xff1f; 二、视图的创建 三、视图的作用 3.1 用视图简化复杂的联结 3.2 用视图格式化检索出的数据 3.3 用视图过滤数据…

数字人系统OEM源码及赚钱方式详解!

当前&#xff0c;数字人直播的热度持续上涨&#xff0c;应用场景日益丰富。而随着数字人直播所蕴含的前景和潜力被不断挖掘一批又一批的创业者纷纷开始入局分羹。其中&#xff0c;数字人系统OEM源码模式作为最为常见的入局方式之一&#xff0c;更是备受瞩目。 所谓数字人系统O…

机器人开源项目分享,助力一户一机器人

最初&#xff0c;因隋炀帝思念心切&#xff0c;命工匠按照柳抃的形象制作了木偶机器人&#xff0c;被认为是历史上最早的机器人之一。这些木偶机器人通过精巧设计的机关&#xff0c;能够执行坐、起、拜、伏等动作。 如今&#xff0c;随着科技的发展&#xff0c;机器人已经广泛…

2024年安全生产月资料合集,抓紧保存!

今年6月&#xff0c;我们将迎来第23个全国“安全生产月”&#xff0c;主题定为“人人讲安全、个个会应急———畅通生命通道”。 为了方便大家组织“安全生产月”活动&#xff0c;做好安全月宣传和培训。我们特别邀请了 安全工程师 王欣和李勇刚&#xff0c;结合今年的活动要求…

pip(包管理器) for Python

pip是什么 pip是Python的包安装程序&#xff0c;即python包管理器。您可以使用 pip 从Python包索引和其他索引安装包。 1. pip 安装 python 包 pip install 包名 例如&#xff1a;pip install pymssql &#xff1a; 使用pip安装数据库驱动包 pymssql 2.pip 卸载 python 包 pi…

LSPatch免root手机模块应用

软件介绍 LSPatch是一款免root手机模块应用&#xff0c;兼容大部分机型&#xff0c;使用LSPatch&#xff0c;您可以个性化您的Android设备&#xff0c;添加新的功能&#xff0c;修改系统设置&#xff0c;甚至完全改变系统的外观。您可以根据自己的需求选择和安装各种Xposed模块…

神经网络优化算法

神经网络优化算法 文章目录 神经网络优化算法梯度下降算法批量梯度下降法随机梯度下降法小批量随机梯度下降法动量法NAGAdaGradRMSPropADADELTAADAMNADAM 梯度下降算法 以 f ( x ) 1 2 x 2 f(x)\frac12x^2 f(x)21​x2为例展示了梯度下降法中梯度下降的实际情况&#xff0c;图…

Postman快捷功能-快速填写请求头

大家好&#xff0c;之前给大家分享关于 Postman 工具的基础使用&#xff0c;今天给大家介绍一个快捷功能&#xff0c;可以一定程度提高我们使用 Postman 工具的效率&#xff0c;在我们进行接口测试时&#xff0c;几乎每个接口都需要填写 Headers&#xff0c;且 Headers 中的参数…