SparseArray是android里为<Interger,Object>这样的Hashmap而专门写的class,目的是提高效率,其核心是折半查找函数(binarySearch)。

HashMap底层是一个Hash表,是数组和链表的集合实现,有需要的可以去看看我关于Hashmap的分析。hashmap源码分析

所以Android开发中官方推荐:当使用HashMap(K, V),如果K为整数类型时,使用SparseArray的效率更高。

那我们看源码来分析下,

构造函数:

/**
 * 存储索引集合.
 */
private int[] mKeys;
/**
 * 存储对象集合.
 */
private Object[] mValues;
/**
 * 存储的键值对总数.
 */
private int mSize;
/**
 * 采用默认的构造函数,则初始容量为10.
 */
public SparseArray() {
    this(10);
}
/**
 * 使用指定的初始容量构造SparseArray.
 *
 * @param initialCapacity 初始容量
 */
public SparseArray(int initialCapacity) {
    if (initialCapacity == 0) {
        // Effective Java中第43条:返回零长度的数组或者集合,而不是:null
        mKeys = ContainerHelpers.EMPTY_INTS;
        mValues = ContainerHelpers.EMPTY_OBJECTS;
    } else {
        // 构造initialCapacity大小的int数组和object数组
        mKeys = new int[initialCapacity];
        mValues = new Object[initialCapacity];
    }
    // 设置SparseArray存储的<key,value>键值对个数为0.
    mSize = 0;
}

和HashMap的数据结构不同,HashMap是使用数组+链表的数据结构存储键值对,而SparseArray只是用了两个数组进行存储。

我们知道链表的时间复杂度是很高的,这估计也是造成hashmap时间复杂度高的一个原因。

ContainerHelpers

ContainerHelpers类提供了二分查找算法,这也一定程度上提高了查找的效率

<span style="font-size:12px;">class ContainerHelpers {
    // This is Arrays.binarySearch(), but doesn't do any argument validation.
    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
    }
}</span>

put()函数

/**
 * 在SparseArray中存储键值对.
 */
public void put(int key, E value) {
    // 通过二分查找算法计算索引
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    if (i >= 0) {
        // key已经存在对应的value,则直接替换value.
        mValues[i] = value;
    } else {
        i = ~i;
        if (i < mSize && mValues[i] == DELETED) {
            // 特殊的case,直接存储key-value即可
            mKeys[i] = key;
            mValues[i] = value;
            return;
        }
        if (mGarbage && mSize >= mKeys.length) {
            // 如果有元素被删除,并且目前容量不足,先进行一次gc
            gc();
            // Search again because indices may have changed.
            i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
        }
        // 扩容
        if (mSize >= mKeys.length) {
            // 获取扩容的数组大小
            int n = mSize + 1;
            int[] nkeys = new int[n];
            Object[] nvalues = new Object[n];
            // 数组拷贝最好使用System.arraycopy,而不是自己重撸一遍
            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
            mKeys = nkeys;
            mValues = nvalues;
        }
        // i为插入位置,如果i<mSize,则i之后的元素需要依次向后移动一位.
        if (mSize - i != 0) {
            System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
            System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
        }
        // 设置值,存储数量+1
        mKeys[i] = key;
        mValues[i] = value;
        mSize++;
    }
}

put函数的逻辑:

  1. 通过二分查找算法,计算key的索引值.
  2. 如果索引值大于0,说明有key对应的value存在,直接替换value即可.
  3. 如果索引值小于0,对索引值取反,获取key应该插入的坐标i.
  4. 判断是否需要扩容:1.需要扩容,则先扩容; 2.不需要扩容,则利用System.arraycopy移动相应的元素,进行(key,value)键值对插入.

get()函数

get函数就是利用二分查找获取key的下标,然后从object[] value数组中根据下标获取值. 

之所以SparseArray号称比HashMap有更好的性能:

  1. SparseArray更加节约内存,一个int[]数组存储所有的key,一个object[] 数组存储所有的value.
  2. HashMap遇到冲突时,时间复杂度为O(n).而SparseArray不会有冲突,采用二分搜索算法,时间复杂度为O(lgn).
/**
 * 根据指定的key获取value.
 */
public E get(int key) {
    return get(key, null);
}
/**
 * 利用二分查找算法根据key获取指定的value.
 */
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];
    }
}

delete()函数

/**
 * 根据key删除指定的value.
 */
public void delete(int key) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

    if (i >= 0) {
        if (mValues[i] != DELETED) {
            // 标记i的值为private static final Object DELETED = new Object();
            mValues[i] = DELETED;
            // 设置gc标记为true.
            mGarbage = true;
        }
    }
}
/**
 * Alias for {@link #delete(int)}.
 */
public void remove(int key) {
    delete(key);
}

gc()函数

if (mGarbage && mSize >= mKeys.length) {
    // 如果有元素被删除,并且目前容量不足,先进行一次gc
    gc();
    // Search again because indices may have changed.
    i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}

通过上面的源码分析,我们不难得出:

正式因为SparseArray采用了数组这种形式,才使得我们的key在做hash运算的时候,通过二分查找时间复杂度降低了,从而提高了效率。
通过二分查找保证查询效率为O(lgn).而HashMap在未冲突的情况下是O(1),冲突的情况下是O(n).



SparseArray到底哪点比HashMap好的更多相关文章

  1. 关于Android中ArrayMap/SparseArray比HashMap性能好的深入研究

    由于网上有朋友对于这个问题已经有了很详细的研究,所以我就不班门弄斧了: 转载于:http://android-performance.com/android/2014/02/10/android-sp ...

  2. Android内存优化(使用SparseArray和ArrayMap代替HashMap)

    在Android开发时,我们使用的大部分都是Java的api,比如HashMap这个api,使用率非常高,但是对于Android这种对内存非常敏感的移动平台,很多时候使用一些java的api并不能达到 ...

  3. 数据结构HashMap(Android SparseArray 和ArrayMap)

    HashMap也是我们使用非常多的Collection,它是基于哈希表的 Map 接口的实现,以key-value的形式存在.在HashMap中,key-value总是会当做一个整体来处理,系统会根据 ...

  4. 【转】HashMap,ArrayMap,SparseArray源码分析及性能对比

    HashMap,ArrayMap,SparseArray源码分析及性能对比 jjlanbupt 关注 2016.06.03 20:19* 字数 2165 阅读 7967评论 13喜欢 43 Array ...

  5. Android内存优化(使用SparseArray和ArrayMap取代HashMap)

    在Android开发时,我们使用的大部分都是Java的api,比方HashMap这个api,使用率非常高,可是对于Android这样的对内存非常敏感的移动平台,非常多时候使用一些java的api并不能 ...

  6. Android学习笔记之性能优化SparseArray

    PS:终于考完试了.来一发.微机原理充满了危机.不过好在数据库89分,还是非常欣慰的. 学习内容: 1.Android中SparseArray的使用..   昨天研究完横向二级菜单,发现其中使用了Sp ...

  7. android小知识之SparseArray(HaspMap替换)

    最近编程时,发现一个针对HashMap<Integer, E>的一个提示: 翻译过来就是:用SparseArray<E>来代替会有更好性能.那我们就来看看源码中SparseAr ...

  8. Android编程之SparseArray<E>详解

    最近编程时,发现一个针对HashMap<Integer, E>的一个提示: 翻译过来就是:用SparseArray<E>来代替会有更好性能.那我们就来看看源码中SparseAr ...

  9. Android 性能优化 SparseArray【转载】

    原文地址:Android学习笔记之性能优化SparseArray 学习内容: 1.Android中SparseArray的使用..   昨天研究完横向二级菜单,发现其中使用了SparseArray去替 ...

随机推荐

  1. iOS不能交互的几种情况

    alpha <=0.01 hidden = YES userInteraction = NO 父试图不允许交互,子试图也不允许交互: 在父试图可见范围内,可以交互,超出部分失效,不能交互

  2. 在Spring Boot框架下使用WebSocket实现聊天功能

    上一篇博客我们介绍了在Spring Boot框架下使用WebSocket实现消息推送,消息推送是一对多,服务器发消息发送给所有的浏览器,这次我们来看看如何使用WebSocket实现消息的一对一发送,模 ...

  3. Vulkan的分层设计

    Vulkan驱动层提供了简单高效的API.作为Vulkan API的使用者,我们要严格遵循Vulkan API的使用规则.如果我们违反了这些规则,Vulkan只会返回很少的反馈,它只会报告一部分严重和 ...

  4. Java程序员的Golang入门指南(下)

    Java程序员的Golang入门指南(下) 4.高级特性 上面介绍的只是Golang的基本语法和特性,尽管像控制语句的条件不用圆括号.函数多返回值.switch-case默认break.函数闭包.集合 ...

  5. 【我的书】《Unity Shader入门精要》出版上市

    重要的事 先说重要的事,就是我的书籍<Unity Shader入门精要>在经过无数次跳票后,终于出版上市了(泪目-)! 购买传送门: 亚马逊 当当 京东 截止到我写这篇文章的时候,京东是没 ...

  6. Google图片加载库Glide的简单封装GlideUtils

    Google图片加载库Glide的简单封装GlideUtils 因为项目里用的Glide的地方比较多,所有简单的封装了以下,其实也没什么,就是写了个工具类,但是还是要把基础说下 Glide的Githu ...

  7. CoreAnimation中layer动画闪烁的原因及解决

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 网上有一段Core Animation层动画的例子,是将vie ...

  8. Java并发框架——AQS之如何使用AQS构建同步器

    AQS的设计思想是通过继承的方式提供一个模板让大家可以很容易根据不同场景实现一个富有个性化的同步器.同步器的核心是要管理一个共享状态,通过对状态的控制即可以实现不同的锁机制.AQS的设计必须考虑把复杂 ...

  9. GridView如何适配不同屏幕

    GridView和ListView一样,都是项目中常用的控件之一,那么本篇文章要讲的是GridView如何适应不同大小的屏幕,首先,我们来看一张效果图,如下: 每行为四个item,上下左右间距大概2d ...

  10. storm消费kafka实现实时计算

    大致架构 * 每个应用实例部署一个日志agent * agent实时将日志发送到kafka * storm实时计算日志 * storm计算结果保存到hbase storm消费kafka 创建实时计算项 ...