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

文章目录

      • 一、`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 在大量数据操作时性能表现良好。
欢迎点赞|关注|收藏|评论,您的肯定是我创作的动力

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【问题记录】QT“类型强制转换“:无法从“ATL::CString“转换为“LPCWSTR“

一&#xff0c;问题现象 环境&#xff1a;VS2019QT 报错提示&#xff1a;“类型强制转换”&#xff1a;无法从"ATL::CString"转换为"LPCWSTR" 二&#xff0c;解决方法 打开项目属性&#xff0c;设置字符集&#xff0c;如下所示&#xff1a;

SQL:学习SQL优化

学习 1.语句 2.原则&#xff08;三条快速记忆&#xff09; 3.常见查询类型 试验 本次试验采用SQL表中的world 数据库中city表来试验 1.查询方法 explain SELECT * FROM city where ID>500 limit 10; #1.all查询&#xff0c;主要是因为查询的键不是District&#xff0c;…

【移花接木】OpenCV4.8 For Java 深度学习 实时人脸检测

学习《OpenCV应用开发&#xff1a;入门、进阶与工程化实践》一书&#xff0c;学会本文所有技能就这么简单&#xff01; 做真正的OpenCV开发者&#xff0c;从入门到入职&#xff0c;一步到位&#xff01; 前言 我写这篇文章之前&#xff0c;我搜索整个网络文章跟问各种语言大模…

【Linux命令】--- 多核压缩命令大全(加快压缩和解压)

在编程的艺术世界里&#xff0c;代码和灵感需要寻找到最佳的交融点&#xff0c;才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里&#xff0c;我们将共同追寻这种完美结合&#xff0c;为未来的世界留下属于我们的独特印记。 【Linux命令】--- 多核压缩命令大全&…

AI播客下载:Dwarkesh Podcast(关于AI的深度访谈)

Dwarkesh Podcast 是由 Dwarkesh Patel 主持的播客&#xff0c;专注于深度访谈和探讨各种复杂且有趣的话题。该播客在业界获得了极高的评价&#xff0c;被认为是对话和思想交流的平台。 Dwarkesh Podcast 的内容涵盖了多个领域&#xff0c;包括经济学、哲学以及科技等。例如&am…

苏州市首批类博物馆授牌,李良济中医药博物馆榜上有名

&#xff15;月18日是国际博物馆日&#xff0c;今年的活动主题是“博物馆&#xff1a;促进社会变化发展的力量”。当天&#xff0c;2024年“518国际博物馆日”苏州主会场活动在苏州御窑金砖博物馆启幕&#xff01; 为了推动全市博物馆蓬勃发展&#xff0c;凝聚社会各方力量&…

微软:最新ChatGPT-4o模型,可在 Azure OpenAI上使用

北京时间5月14日凌晨&#xff0c;OpenAI 一场不到 30 分钟的发布会&#xff0c;正式发布了 GPT-4o&#xff0c;视频语音交互丝滑到吓人&#xff0c;还即将免费可用&#xff01; GPT-4o&#xff0c;其中的「o」代表「omni」&#xff08;即全面、全能的意思&#xff09;&#xff…

某勾求职网逆向分析

搜索目标: aHR0cHM6Ly93d3cubGFnb3UuY29tL3duL2pvYnM/cG49MSZweD1kZWZhdWx0JmZyb21TZWFyY2g9dHJ1ZSZrZD0lRTYlOTUlQjAlRTYlOEQlQUUlRTUlODglODYlRTYlOUUlOTA= 抓包分析 请求和返回都是加密的 请求头部也有未知参数 跟栈分析 请求和返回是一个AES加密,加密的KEY是session s…

提升主播直播体验:如何选择和使用第三方美颜SDK?

第三方美颜SDK为开发者提供了实现这些功能的便利途径。那么&#xff0c;如何选择和使用第三方美颜SDK&#xff0c;来提升主播的直播体验呢&#xff1f; 一、了解美颜SDK的重要性 1.1美颜SDK的作用 美颜SDK不仅能提升主播的自信&#xff0c;还能吸引更多观众&#xff0c;增加…

Color预设颜色测试

"AliceBlue", "获取 ARGB 值为 的系统 #FFF0F8FF定义颜色。", "AntiqueWhite", "获取 ARGB 值为 的系统 #FFFAEBD7定义颜色。", "Aqua", "获取 ARGB 值为 的系统 #FF00FFFF定义颜色。", "Aquamarine"…

接口自动化测试工具-----pytest

首先确保安装了Python环境&#xff0c;首先&#xff0c;你需要确保已安装 Python 和 Pip。如果还没有安装&#xff0c;可以从 Python 官方网站下载并安装最新版本的 Python。安装过程中请确保选中“Add Python to PATH”选项。 安装pytest:打开命令提示符&#xff08;Command …

linux系统CPU持续飙高的排查方法

目录 前言&#xff1a; 1、查看系统cpu使用情况 2、找出占用cpu高的进程 3、进一步分析进程占用的原因&#xff01;&#xff01;&#xff01; 4、解决办法 前言&#xff1a; 如果一台服务器&#xff0c;它的cpu使用率一直处于一个高峰值&#xff0c;此时服务器可能导致无…

直击三大实体瘤!上海交大团队发布深度学习系统,提高癌症生存预测准确性

世界卫生组织 2022 年发布的报告指出&#xff0c;癌症等非传染性疾病 (NCDs)) 已超过传染病&#xff0c;成为「全球头号杀手」。 中国国家癌症中心发布的最新数据显示&#xff0c;2022 年中国约有 482.47 万新发癌症病例和 257.42 万新发癌症死亡病例。 很长一段时间里&#x…

ASP.Net MVC在控制台添加视图时没有模型类并且不能添加视图

情况如下&#xff1a; 解决方法&#xff1a; 1.查看vs能否创建asp.net mvc项目&#xff0c;这种情况一般是更换了vs打开老项目 2.点击跳转至修改安装选项界面 3.选择安装项即可 如果以上都有&#xff1a; 看看你的视图文件是否存在在项目中 也不能点击添加&#xff0c;如果…

免费,Python蓝桥杯等级考试真题--第9级(含答案解析和代码)

Python蓝桥杯等级考试真题–第9级 一、 选择题 答案&#xff1a;C 解析&#xff1a;最外层for循环控制行数&#xff0c;range&#xff08;0,7&#xff09;可以输出7行&#xff0c;故答案为C。 答案&#xff1a;A 解析&#xff1a;第一层for循环可以产生5行&#xff0c;第二层…

AI办公自动化-kimi批量在多个Excel工作表中绘制柱状图

工作任务和目标&#xff1a;批量在多个Excel工作表中生成一个柱状图 第一步&#xff0c;在kimi中输入如下提示词&#xff1a; 你是一个Python编程专家&#xff0c;完成下面任务的Python脚本&#xff1a; 打开文件夹&#xff1a;F:\aivideo 读取里面所有的xlsx文件&#xff1…

C++ RPC ORM 高速解析

支持所有常用编程语 https://capnproto.org/GitHub - capnproto/capnproto: Capn Proto serialization/RPC system - core tools and C library https://capnproto.org/capnproto-c-win32-1.0.2.zip 常用命令&#xff1a; capnp help capnp compile -oc myschema.capn…

vue3的核心API功能:computed()API使用

常规使用方法: 这样是常规使用方法. 另一种使用方法: 这样分别定义computed的get回调函数和set回调函数, 上面例子定义了plusOne.value的值为1, 那么这时候就走了computed的set回调函数,而没有走get回调函数. 当我们打印plusOne.value的值的时候,走的是get的回调函数而不是…

如何将手机中的音乐转移到 SD 卡上?轻松传输音乐

概括 如何将音乐从手机转移到 SD 卡&#xff1f;我们的智能手机可以充当个人点唱机&#xff0c;因此有效管理我们的音乐库变得至关重要。无论您是存储空间不足还是只是想整理您的音乐收藏&#xff0c;将音乐从手机传输到 SD 卡都是一个实用的解决方案。 在本指南中&#xff0…

vue项目报错:internal/modules/cjs/loader.js:892 throw err;

前言&#xff1a; vue项目中无法正常使用git&#xff0c;并报错情况。 报错信息&#xff1a; internal/modules/cjs/loader.js:892throw err;^ Error: Cannot find module D:\project\sd_wh_yth_front\node_modules\yorkie\src\runner.js 报错处理&#xff1a; npm install y…