Android学习笔记之性能优化SparseArray
PS:终于考完试了.来一发.微机原理充满了危机.不过好在数据库89分,还是非常欣慰的.
学习内容:
1.Android中SparseArray的使用..
昨天研究完横向二级菜单,发现其中使用了SparseArray去替换HashMap的使用.于是乎自己查了一些相关资料,自己同时对性能进行了一些测试。首先先说一下SparseArray的原理.
SparseArray(稀疏数组).他是Android内部特有的api,标准的jdk是没有这个类的.在Android内部用来替代HashMap<Integer,E>这种形式,使用SparseArray更加节省内存空间的使用,SparseArray也是以key和value对数据进行保存的.使用的时候只需要指定value的类型即可.并且key不需要封装成对象类型.
楼主根据亲测,SparseArray存储数据占用的内存空间确实比HashMap要小一些.一会放出测试的数据在进行分析。我们首先看一下二者的结构特性.
HashMap是数组和链表的结合体,被称为链表散列.
SparseArray是单纯数组的结合.被称为稀疏数组,对数据保存的时候,不会有额外的开销.结构如下:
这就是二者的结构,我们需要看一下二者到底有什么差异...
首先是插入:
HashMap的正序插入:
HashMap<Integer, String>map = new HashMap<Integer, String>();
long start_map = System.currentTimeMillis();
for(int i=0;i<MAX;i++){
map.put(i, String.valueOf(i));
}
long map_memory = Runtime.getRuntime().totalMemory();
long end_map = System.currentTimeMillis()-start_map;
System.out.println("<---Map的插入时间--->"+end_map+"<---Map占用的内存--->"+map_memory); 执行后的结果:
<---Map的插入时间--->914
<---Map占用的内存--->28598272
SparseArray的正序插入:
SparseArray<String>sparse = new SparseArray<String>();
long start_sparse = System.currentTimeMillis();
for(int i=0;i<MAX;i++){
sparse.put(i, String.valueOf(i));
}
long sparse_memory = Runtime.getRuntime().totalMemory();
long end_sparse = System.currentTimeMillis()-start_sparse;
System.out.println("<---Sparse的插入时间--->"+end_sparse+"<---Sparse占用的内存--->"+sparse_memory); //执行后的结果:
<---Sparse的插入时间--->611
<---Sparse占用的内存--->23281664
我们可以看到100000条数据量正序插入时SparseArray的效率要比HashMap的效率要高.并且占用的内存也比HashMap要小一些..这里的正序插入表示的是i的值是从小到大进行的一个递增..序列取决于i的值,而不是for循环内部如何执行...
通过运行后的结果我们可以发现,SparseArray在正序插入的时候,效率要比HashMap要快得多,并且还节省了一部分内存。网上有很多的说法关于二者的效率问题,很多人都会误认为SparseArray要比HashMap的插入和查找的效率要快,还有人则是认为Hash查找当然要比SparseArray中的二分查找要快得多.
其实我认为Android中在保存<Integer,Value>的时候推荐使用SparseArray的本质目的不是由于效率的原因,而是内存的原因.我们确实看到了插入的时候SparseArray要比HashMap要快.但是这仅仅是正序插入.我们来看看倒序插入的情况.
HashMap倒序插入:
System.out.println("<------------- 数据量100000 散列程度小 Map 倒序插入--------------->");
HashMap<Integer, String>map_2 = new HashMap<Integer, String>();
long start_map_2 = System.currentTimeMillis();
for(int i=MAX-1;i>=0;i--){
map_2.put(MAX-i-1, String.valueOf(MAX-i-1));
}
long map_memory_2 = Runtime.getRuntime().totalMemory();
long end_map_2 = System.currentTimeMillis()-start_map_2;
System.out.println("<---Map的插入时间--->"+end_map_2+"<---Map占用的内存--->"+map_memory_2); //执行后的结果:
<------------- 数据量100000 Map 倒序插入--------------->
<---Map的插入时间--->836<---Map占用的内存--->28598272
SparseArray倒序插入:
System.out.println("<------------- 数据量100000 散列程度小 SparseArray 倒序插入--------------->");
SparseArray<String>sparse_2 = new SparseArray<String>();
long start_sparse_2 = System.currentTimeMillis();
for(int i=MAX-1;i>=0;i--){
sparse_2.put(i, String.valueOf(MAX-i-1));
}
long sparse_memory_2 = Runtime.getRuntime().totalMemory();
long end_sparse_2 = System.currentTimeMillis()-start_sparse_2;
System.out.println("<---Sparse的插入时间--->"+end_sparse_2+"<---Sparse占用的内存--->"+sparse_memory_2);
//执行后的结果
<------------- 数据量100000 SparseArray 倒序插入--------------->
<---Sparse的插入时间--->20222<---Sparse占用的内存--->23281664
通过上面的运行结果,我们仍然可以看到,SparseArray与HashMap无论是怎样进行插入,数据量相同时,前者都要比后者要省下一部分内存,但是效率呢?我们可以看到,在倒序插入的时候,SparseArray的插入时间和HashMap的插入时间远远不是一个数量级.由于SparseArray每次在插入的时候都要使用二分查找判断是否有相同的值被插入.因此这种倒序的情况是SparseArray效率最差的时候.
SparseArray的插入源码我们简单的看一下..
public void put(int key, E value) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key); //二分查找. if (i >= 0) { //如果当前这个i在数组中存在,那么表示插入了相同的key值,只需要将value的值进行覆盖..
mValues[i] = value;
} else { //如果数组内部不存在的话,那么返回的数值必然是负数.
i = ~i; //因此需要取i的相反数.
//i值小于mSize表示在这之前. mKey和mValue数组已经被申请了空间.只是键值被删除了.那么当再次保存新的值的时候.不需要额外的开辟新的内存空间.直接对数组进行赋值即可.
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
//当需要的空间要超出,但是mKey中存在无用的数值,那么需要调用gc()函数.
if (mGarbage && mSize >= mKeys.length) {
gc(); // Search again because indices may have changed.
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
//如果需要的空间大于了原来申请的控件,那么需要为key和value数组开辟新的空间.
if (mSize >= mKeys.length) {
int n = ArrayUtils.idealIntArraySize(mSize + 1);
//定义了一个新的key和value数组.需要大于mSize
int[] nkeys = new int[n];
Object[] nvalues = new Object[n]; // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
//对数组进行赋值也就是copy操作.将原来的mKey数组和mValue数组的值赋给新开辟的空间的数组.目的是为了添加新的键值对.
System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
//将数组赋值..这里只是将数组的大小进行扩大..放入键值对的操作不在这里完成.
mKeys = nkeys;
mValues = nvalues;
}
//如果i的值没有超过mSize的值.只需要扩大mKey的长度即可.
if (mSize - i != 0) {
// Log.e("SparseArray", "move " + (mSize - i));
System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
}
//这里是用来完成放入操作的过程.
mKeys[i] = key;
mValues[i] = value;
mSize++;
}
}
这就是SparseArray插入函数的源码.每次的插入方式都需要调用二分查找.因此这样在倒序插入的时候会导致情况非常的糟糕,效率上绝对输给了HashMap学过数据结构的大家都知道.Map在插入的时候会对冲突因子做出相应的决策.有非常好的处理冲突的方式.不需要遍历每一个值.因此无论是倒序还是正序插入的效率取决于处理冲突的方式,因此插入时牺牲的时间基本是相同的.
通过插入.我们还是可以看出二者的差异的.
我们再来看一下查找首先是HashMap的查找.
System.out.println("<------------- 数据量100000 Map查找--------------->");
HashMap<Integer, String>map = new HashMap<Integer, String>(); for(int i=0;i<MAX;i++){
map.put(i, String.valueOf(i));
}
long start_time =System.currentTimeMillis();
for(int i=0;i<MAX;i+=100){
map.get(i);
}
long end_time =System.currentTimeMillis()-start_time;
System.out.println(end_time); //执行后的结果
<!---------查找的时间:175------------>
SparseArray的查找:
System.out.println("<------------- 数据量100000 SparseArray 查找--------------->");
SparseArray<String>sparse = new SparseArray<String>();
for(int i=0;i<10000;i++){
sparse.put(i, String.valueOf(i));
}
long start_time =System.currentTimeMillis(); for(int i=0;i<MAX;i+=10){
sparse.get(i);
}
long end_time =System.currentTimeMillis()-start_time;
System.out.println(end_time);
//执行后的结果
<!-----------查找的时间:239---------------->
我这里也简单的对查找的效率进行了测试.对一个数据或者是几个数据的查询.二者的差异还是非常小的.当数据量是100000条.查100000条的效率还是Map要快一点.数据量为10000的时候.这就差异性就更小.但是Map的查找的效率确实还是赢了一筹.
其实在我看来.在保存<Integer,E>时使用SparseArray去替换HashMap的主要原因还是因为内存的关系.我们可以看到.保存的数据量无论是大还是小,Map所占用的内存始终是大于SparseArray的.数据量100000条时SparseArray要比HashMap要节约27%的内存.也就是以牺牲效率的代价去节约内存空间.我们知道Android对内存的使用是极为苛刻的.堆区允许使用的最大内存仅仅16M.很容易出现OOM现象的发生.因此在Android中内存的使用是非常的重要的.因此官方才推荐去使用SparseArray<E>去替换HashMap<Integer,E>.官方也确实声明这种差异性不会超过50%.所以牺牲了部分效率换来内存其实在Android中也算是一种很好的选择吧.
Android学习笔记之性能优化SparseArray的更多相关文章
- IOS学习笔记45--UITableView性能优化
说实话,面试的时候已经被问到几次这个问题,然后就搜索了一下,看到了这篇优化文章,感觉不错,转来日后作为一种UITableView优化的方法. 使用不透明视图. 不透明的视图可以极大地提高渲染 ...
- Android 性能优化 SparseArray【转载】
原文地址:Android学习笔记之性能优化SparseArray 学习内容: 1.Android中SparseArray的使用.. 昨天研究完横向二级菜单,发现其中使用了SparseArray去替 ...
- Android App性能优化笔记之一:性能优化是什么及为什么?
By Long Luo 周星驰的电影<功夫>里面借火云邪神之口说出了一句至理名言:“天下武功,唯快不破”. 在移动互联网时代,同样如此,留给一个公司的窗口往往只有很短的时间,如何把握住 ...
- Android比较实用的性能优化
Android设备作为一种移动设备,无论是内存还是CPU的性能都受到了很大的限制,这导致Android程序的性能问题异常突出,随着产品的不断更新迭代,对于性能优化提出了更高的要求.本篇文章从稳定性.流 ...
- 在 Android开发中,性能优化策略十分重要
在 Android开发中,性能优化策略十分重要本文主要讲解性能优化中的布局优化,希望你们会喜欢.目录 示意图 1. 影响的性能 布局性能的好坏 主要影响 :Android应用中的页面显示速度 2. 如 ...
- 【转】Pro Android学习笔记(三十):Menu(1):了解Menu
目录(?)[-] 创建Menu MenuItem的属性itemId MenuItem的属性groupId MenuItem的属性orderId MenuItem的属性可选属性 Menu触发 onOpt ...
- 【转】 Pro Android学习笔记(二二):用户界面和控制(10):自定义Adapter
目录(?)[-] 设计Adapter的布局 代码部分 Activity的代码 MyAdapter的代码数据源和构造函数 MyAdapter的代码实现自定义的adapter MyAdapter的代码继续 ...
- 【转】Pro Android学习笔记(二):开发环境:基础概念、连接真实设备、生命周期
在Android学习笔记(二):安装环境中已经有相应的内容.看看何为新.这是在source网站上的Android架构图,和标准图没有区别,只是这张图颜色好看多了,录之.本笔记主要讲述Android开发 ...
- Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- Direct12优化
原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- Direct12优化 第一章:向量代数 1.向量计算的时候,使用XMV ...
随机推荐
- Android的消息机制: Message/MessageQueue/Handler/Looper
概览 * Message:消息.消息里面可包含简单数据.Object和Bundle,还可以包含一个Runnable(实际上可看做回调). * MessageQueue:消息队列,供Looper线程 ...
- TCP/IP 端口号大全
常用端口: 20 ftp-data FTP 数据端口 21 ftp 文件传输协议(FTP)端口:有时被文件服务协议(FSP)使用 22 ssh 安全 Shell(SSH)服务 23 telnet T ...
- C#控制其它程序
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)] ...
- Android基本功:手势
一.概念 手势:其实是指用户手指或触摸笔在屏幕上的连续触碰行为,Andoird对两种手势行为都提供了支持: Andorid提供了手势检测,并为手势检测提供了相应的监听器: Android允许开发者添加 ...
- mybatis热加载的实现
最近在使用mybatis,由于是刚刚开始用,用的并不顺手,目前是感觉有2个地方非常的不好用: 1.mybatis调试不方便 由于dao层只有接口,实现只是一个map的xml文件,想加断点都没有地方加, ...
- php中如何创建文件夹
这个功能比较简单,直接上代码了: $dir = iconv("UTF-8", "GBK", "Public/bookcover"); if ...
- [LeetCode] Find Median from Data Stream
Find Median from Data Stream Median is the middle value in an ordered integer list. If the size of t ...
- TextView使用SpannableString设置复合文本(转)
TextView通常用来显示普通文本,但是有时候需要对其中某些文本进行样式.事件方面的设置.Android系统通过SpannableString类来对指定文本进行相关处理,具体有以下功能: 1.Bac ...
- 资源监控工具Spotlight-使用说明
几年前使用过此工具,发现用于监控远程服务器,非常方面而且快捷.当前再次用于配合压力测试进行资源监控,突然想起来了,以免生疏,在此记录! 1.被监控服务器为Ubuntu server,先在服务器上创建一 ...
- Oracle 10gR2 & 10.2.0.5 的百度网盘下载地址 :)
如题: https://pan.baidu.com/s/1eSI770m