IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> SparseArray 源码分析 -> 正文阅读

[移动开发]SparseArray 源码分析

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

因为泛型擦除的原因基本数据类型不能作为泛型参数,如果需要就得使用它们的包装类就带来了装箱拆箱的开销,为了解决这个问题才有了 SparseArray 相关的几个类,SparseArray 比 HashMap 要慢一些是时间换空间的实现。


提示:基于 Android SDK 28

一、SparseArray 是什么 ?

SparseArray 是 Android 平台用来存储 key 为 int ,value 为 Object 的数据结构,它的内部实现是两个数组,一个 int 数组保存有序的 key 一个 Object 数组保存 value 并且 key 与 value 所在数组中的索引位置一致。另外为了提高性能对删除做了优化,当删除一个数据时并不是立即从 value 中删除而是先标记为删除,当对应的位置再次存储数据时可以直接复用这个空间避免每次删除添加元素时都移动元素

二、源码分析

1.delete

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

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

通过二分查找拿到指定 key 在 key 数组中的位置,如果 i 大于 0 说明指定 key 存在,如果指定位置的 value 没有标记为删除则标记为删除并且把 mGarbage 标记为 true 表示可以进行垃圾回收

2.put

    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();

                // Search again because indices may have changed.
                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
            }

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

    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
    }

binarySearch 方法的作用是如果指定的 key 存在则返回指定 key 的位置否则返回 key 应该插入位置的取反值。如果 i 大于 0 说明指定的位置已经存储了值直接覆盖 value,否则对 i 取反得到应该插入的位置,如果这个位置是删除标记则直接覆盖 key 和 value ,否则判断如果数组有已经满了并且数组已经满了则调用 gc 把 “稀疏数组” 变为正常数组

    private void gc() {
        // Log.e("SparseArray", "gc start with " + mSize);

        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;

        // Log.e("SparseArray", "gc end with " + mSize);
    }

gc 方法的作用是移动有效数据把标记为删除的数据覆盖,使 “稀疏数组” 变成连续数组并且重新记录 mSize 的值并且把 mGarbage 置为 false,进行了 gc 操作之后因为数据变化再次使用二分查找拿到 key 应该插入的位置,以为 key 可能不存在所以直接取反再把 key value 插入到数组中

    public static int[] insert(int[] array, int currentSize, int index, int element) {
        assert currentSize <= array.length;
		// 如果不需要扩容
        if (currentSize + 1 <= array.length) {
        	// 把数组中应该插入位置和之后位置的数据都向后移动
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            // 把数据插入到指定位置
            array[index] = element;
            // 返回自己
            return array;
        }

		// 如果需要扩容创建新的指定大小的数组
        int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
        // 把老数组中的指定位置前的元素复制到新数组
        System.arraycopy(array, 0, newArray, 0, index);
        // 把元素存储到新数组的指定位置
        newArray[index] = element;
        // 复制老数组指定位置和之后位置的元素到新数组
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        // 返回新数组
        return newArray;
    }
	
	// 根据当前数组大小返回扩容后的数组大小
    public static int growSize(int currentSize) {
    	// 如果当前数组大小小于 4 则返回 8 否则返回当前数组大小 * 2 
        return currentSize <= 4 ? 8 : currentSize * 2;
    }

insert 做了真正的数据存储的工作,判断是否需要扩容把元素存储到指定位置,扩容规则也大致是当前容量的 2 倍

3.append

    public void append(int key, E value) {
        if (mSize != 0 && key <= mKeys[mSize - 1]) {
            put(key, value);
            return;
        }

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

        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
        mValues = GrowingArrayUtils.append(mValues, mSize, value);
        mSize++;
    }

有一种情况,要插入的 key 比 key 数组中所有的元素都大,append 对这种场景做了优化直接调用 GrowingArrayUtils.append 插入元素而不需要进行二分查找并且减少了移动数组的操作,所以应优先使用 append 添加数据

4.get

    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];
        }
    }

get 操作比较简单如果通过 key 找到了对应位置并且对应位置的 value 没有标记为删除则返回对应位置的 value 否则返回指定的默认值

5.其他

  • size 返回有效元素的个数
  • clear 清除所有元素 mSize 置为 0 mGarbage 置为 false
  • indexOfKey 返回指定 key 在数组中的索引找不到则返回负值
  • indexOfValue 返回指定 value 在数组中的索引找不到则返回 -1(使用 == 比较元素的值)
  • indexOfValueByValue 返回指定 value 在数组中的索引找不到则返回 -1(使用 equals 比较元素的值)
  • keyAt 获取指定位置的 key
  • valueAt 获取指定位置的 value
  • setValueAt 设置指定位置的 value 的值
  • remove 与 delete 一致
  • removeAt 将指定位置的值标记为删除
  • removeAtRange 将指定区域的值标记为删除

三、最后

SparseArray 还有几个其他变体

  • SparseIntArray 是 key 为 int 类型 value 为 int 类型容器
  • SparseLongArray 是 key 为 int 类型 value 为 long 类型容器
  • SparseBooleanArray 是 key 为 int 类型 value 为 boolean 类型容器
  • LongSparseArray 是 key 为 long 类型 value 为 Object 类型的容器

SparseArray 的使用场景

  • 数据量不大(千以内)
  • 空间比时间重要
  • 需要使用 key 为 int 类型的容器

参考

面试必备:SparseArray源码解析
这一次,彻底搞懂SparseArray实现原理
面试还在问 SparseArray?记住 3 句话让你临时把佛脚抱好!
Android数据容器之SparseArray
内存优化:充满矛盾的SparseArray

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-07 13:57:32  更:2021-10-07 13:58:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:38:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码