源作者:Android小Y
链接:https://www.jianshu.com/p/1828f14d7955
来源:简书

前言

Android开发中,一个好的应用,除了要有吸引人的功能和交互之外,在性能上也应该有高的要求,如果单单实现页面和业务功能只是完成了基本任务,Android系统对内存要求也是非常高的,稍不注意,就会发生某个页面绘制突然发生卡顿甚至OOM,这对产品的用户体验都是致命性的打击,这就需要我们在日常开发中注意性能方面的优化。

正文

Android开发中经常会使用一些数据结构来存储内存中的数据,其中HashMap是以键值对的形式进行存储,使用频率也非常高,它可以根据key方便地操作集合中的数据,但是由于HashMap内部是通过Hash扩容来开拓内存空间,在内存资源及其珍贵的Android系统中就不能达到很好的效果,所以Android推出了SparseArray,它是android.util包下特有的类,在某些场景下,它比HashMap占用更少的内存。

为何HashMap占用内存较大?

为何SparseArray会比HashMap更节省内存,这要从它们各自的结构说起。HashMap底层数据结构是一个 数组+链表 的组合(关于数组和链表的概念,这里就不多阐述了),它采用一种所谓的“Hash 算法”来决定每个元素的存储位置。当程序执行 map.put(key,Obect) 方法 时,系统将调用key对象的 hashCode() 方法得到其 hashCode 值(每个Java对象都有 hashCode() 方法,都可通过该方法获得它的 hashCode 值)。得到这个对象的 hashCode 值之后,系统会根据该 hashCode 值再hash一遍来决定该元素在数组中的存储位置。

但是这就存在一个问题,如果两个key算出来的hash值刚好相等,也就是存放的数组位置一样时,就产生了Hash冲突(因为原本数组的那个位置已经有一个元素存放着,而一个位置只能存放一组数据),那HashMap是怎么解决这种冲突的呢?
HashMap采用链表法来解决Hash冲突,也就是说,如果发生这种情况,HashMap会在数组中冲突的那个位置,将后加入的元素指向原来占有数组位置的那个元素,从而追加形成一个链表:

HashMap

HashMap中初始的存储大小就是一个容量为16的数组,所以当我们创建出一个HashMap对象时,即使里面没有不论什么元素。也要分别一块内存空间给它,并且,我们再不断的向HashMap里put数据时,当达到一定的容量限制时,HashMap的空间将会扩大为原来的2倍,所以HashMap是比较占内存的。

为何SparseArray更为优化?

先了解一个基本概念——什么是自动装箱?
自动装箱就是指自动将基本数据类型转换为包装器类型,比如下面这句代码:

  1. Integer i = 99;

99是基本数据类型,将它直接赋值给Integer类型对象i时,就会自动将我们的基本类型int包装成Integer。装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能。


而SparseArray又称为稀疏数组,与HashMap不同,其内部是直接通过维护两个数组来实现存储:

  1. public class SparseArray<E> implements Cloneable {
  2.  
  3. private int[] mKeys;
  4. private Object[] mValues;
  5. ...
  6. }

可以看到,一组存储键,一组存储值,key数组的类型是int型,也就是说,SparseArray只支持key为int类型的数据存储,关键就在这里,由于它是直接维护了一个int数组,那么key就避免了自动装箱的过程,举个例子,比如我们用HashMap存储下面这组数据:

  1. HashMap<Integer, String> hashMap = new HashMap<>();
  2. hashMap.put(1, "test");
  3. hashMap.put(2, "test");
  4. hashMap.put(3, "test");

每次put进去的时候,由于传进去的是1,2,3,都是int基本类型,HashMap会自动帮我们包装成Integer类型的对象(也就是刚说的自动装箱),那么就肯定会消耗更多内存。但如果是SparseArray来存储的话,就直接将key存储在key数组了,省去了装箱这个过程,从而节省了内存开销。

另一方面,对SparseArray增删查改操作时,其内部会不断检查回收无用空间,从而压缩占用的内存大小,我们看下它的put方法:

  1. public void put(int key, E value) {
  2. //先调用二分法查询该key在数组中的位置
  3. int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
  4.  
  5. if (i >= 0) {
  6. //大于0说明已经存在数组中,可以直接赋值
  7. mValues[i] = value;
  8. } else {
  9. //小于0说明这是一个新的键值对,且它应该插在数组中的第i个位置
  10. i = ~i;
  11. //根据DELETED来查询当前位置的值是否已经被删除
  12. if (i < mSize && mValues[i] == DELETED) {
  13. mKeys[i] = key;
  14. mValues[i] = value;
  15. return;
  16. }
  17. //如果当前容量已满
  18. if (mGarbage && mSize >= mKeys.length) {
  19. //回收无效空间
  20. gc();
  21.  
  22. // Search again because indices may have changed.
  23. i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
  24. }
  25.  
  26. mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
  27. mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
  28. mSize++;
  29. }
  30. }

可以看到,SparseArray会先调用二分法去查询key应该存放在数组中的位置,所以SparseArray的key数组一定是有序排列的,然后会用一个DELETED来作为当前位置的元素是否已经被删除,DELETED会在调用remove移除元素的时候赋给对应位置Value,如下:

  1. public void remove(int key) {
  2. delete(key);
  3. }
  4.  
  5. public void delete(int key) {
  6. int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
  7.  
  8. if (i >= 0) {
  9. if (mValues[i] != DELETED) {
  10. mValues[i] = DELETED;
  11. mGarbage = true;
  12. }
  13. }
  14. }

SparseArray通过这个来作为它压缩空间的一个标志(即该位置可不可以被回收),这样子也进一步节省了空间。

从刚才可以看出,无论是SparseArray的put还是delete(其实其他操作比如get也都是通过二分法寻找下标),都是通过二分法去查询这个key应该被存放的位置。而HashMap在插入的时候,不需要去遍历整个集合,而是直接通过hash计算出位置插入。所以在插入效率上,SparseArray会比HashMap稍慢一些,但在数据量不大的情况下,两者的差别不大。

结语

SparseArrayHashMap相比,最大的优势在于内存方面,无论数据量级大小如何,SparseArray所占用的内存都会比HashMap小,在Android中内存是极为重要的,所以在需要保存<Integer,Object>键值对的场景中,推荐使用SparseArray替换HashMap。换句话说,SparseArray是Android中为<Integer,Object>这样的HashMap专门写的类,它避开了自动装箱并且压缩稀疏数组,目的就是为了节省内存。
另外,Android还提供了其他几种类似的集合类:SparseIntArraySparseBooleanArraySparseLongArray,可以支持存储<Integer,Integer>、<Integer,Boolean>、<Integer,Long>的数据类型,也就是同时让Value也避开了装箱过程,进一步优化。

Android开发 使用SparseArray代替HashMap[转载]的更多相关文章

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

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

  2. 【移动开发】SparseArray替代HashMap

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

  3. android开发的权限获取 (转载的)

    访问登记属性 android.permission.ACCESS_CHECKIN_PROPERTIES ,读取或写入登记check-in数据库属性表的权限 获取错略位置 android.permiss ...

  4. Android:使用SparseArray取代HashMap优化性能

    之前看到一篇关于adapter的文章用到了SparseArray,所以在这里写写关于SparseArray的使用方法. SparseArray是官方针对安卓所写的容器,与HashMap类似,只是性能比 ...

  5. QT 5.1.1 for Android 开发环境搭建与配置【Windows 7】

    前言:本人操作系统为Windows7 64位,用的是32位的安装包,32位系统没有验证. 一.首先下载以下安装包,如果提供的链接失效请自行下载: (1) Android SDK (Windows 32 ...

  6. 《ArcGIS Runtime SDK for Android开发笔记》——(4)、基于Android Studio构建ArcGIS Android开发环境

    1.前言 2015年1月15日,发布ArcGIS Runtime SDK for Android v10.2.5版本.从该版本开始默认支持android studio开发环境,示例代码的默认开发环境也 ...

  7. Android 开发性能优化之SparseArray(一)

    多数Android开发者都知道在Android中可以使用HashMap来映射一种对应关系,在java开发中HashMap也算是一种很重要的数据存储结构.然后很多人在Android开发中多数都会用Has ...

  8. Android 开发性能优化之SparseArray(三)

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

  9. 50个Android开发人员必备UI效果源码[转载]

    50个Android开发人员必备UI效果源码[转载] http://blog.csdn.net/qq1059458376/article/details/8145497 Android 仿微信之主页面 ...

随机推荐

  1. 让nginx支持patchinfo,(支持codeigniter,thinkphp,ZF等框架)

    nginx 的config配置: server { listen ; server_name xxx; ....if (!-e $request_filename) { rewrite ^/(.*)$ ...

  2. iOS Undefined symbols for architecture armv7:

    armv6 iPhone.iPhone 3G iPod 1G.iPod 2G armv7 iPhone 3GS.iPhone 4 iPod 3G.iPod 4G.iPod 5G iPad.iPad 2 ...

  3. Greenplum(PostgreSql)中函数内游标的使用实例

    直接上代码,具体整体函数定义就不上了,只写关键部分: --定义两个变量 DECLARE CCUR REFCURSOR; -- 游标变量 RECORD1 RECORD; -- 记录变量,用来存储游标遍历 ...

  4. Java-Class-@I:org.junit.Test

    ylbtech-Java-Class-@I:org.junit.Test 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部 1. package org.junit; import ...

  5. web应用本质

    web应用的本质 在之前学习的socket网络编程中,是基于: 架构:C/S架构 协议:TCP/UDP协议 运行在OSI七层模型中的传输层 那么在web应用中,是基于: 架构:B/S架构 协议:Htt ...

  6. topjui.common.js

    function getTabWindow() { var curTabWin = null; if (topJUI.config.aloneUse) { curTabWin = window; } ...

  7. sudo apt-get update:Could not get lock /var/lib/apt/lists/lock解决办法

    原文: http://blog.chinaunix.net/uid-26932153-id-3193335.html 今天更新时候出现了点小问题,一开始更新到一半,我嫌速度慢,就取消掉了. 更新了so ...

  8. 高并发神器 Nginx,到底该怎么学?

    Java技术栈 www.javastack.cn 优秀的Java技术公众号 无论开发还是运维,工作上都会遇到性能优化.高并发的问题,而Nginx是一个万能药,它可以在百万并发连接下实现高吞吐量的 We ...

  9. Spring Boot Server容器配置

    参数配置容器 server.xx开头的是所有servlet容器通用的配置,server.tomcat.xx开头的是tomcat特有的参数,其它类似. 所有参数绑定配置类:org.springframe ...

  10. ctrl+shift+k取消

    因为typora软件和搜狗输入法软件的快捷键重合了,ctrl+shift+k在typora中是代码块的快捷键,而在搜狗输入法中是软键盘快捷键,显然软键盘不重要. 搜狗输入法的ctrl+shift+k取 ...