HashMap,ArrayMap,SparseArray源码分析及性能对比

jjlanbupt 关注

2016.06.03 20:19* 字数 2165 阅读 7967评论 13喜欢 43

ArrayMap及SparseArray是android的系统API,是专门为移动设备而定制的。用于在一定情况下取代HashMap而达到节省内存的目的。

一.源码分析(由于篇幅限制,源码分析部分会放在单独的文章中)
二.实现原理及数据结构对比 
三.性能测试对比
四.总结

一.源码分析
稍后会在下一篇文章中补充(都写在一篇,篇幅太长了)

二.实现原理及数据结构对比
1. hashMap

 
Paste_Image.png

从hashMap的结构中可以看出,首先对key值求hash,根据hash结果确定在table数组中的位置,当出现哈希冲突时采用开放链地址法进行处理。Map.Entity的数据结构如下:

static class HashMapEntry<K, V> implements Entry<K, V> {
final K key;
V value;
final int hash;
HashMapEntry<K, V> next;
}

具体的hashmap源码细节会在其他文章中进行分析,这里可以看出来的是,从空间的角度分析,HashMap中会有一个利用率不超过负载因子(默认为0.75)的table数组,其次,对于HashMap的每一条数据都会用一个HashMapEntry进行记录,除了记录key,value外,还会记录下hash值,及下一个entity的指针。
时间效率方面,利用hash算法,插入和查找等操作都很快,且一般情况下,每一个数组值后面不会存在很长的链表(因为出现hash冲突毕竟占比较小的比例),所以不考虑空间利用率的话,HashMap的效率非常高。

2.ArrayMap

 
Paste_Image.png

ArrayMap利用两个数组,mHashes用来保存每一个key的hash值,mArrray大小为mHashes的2倍,依次保存key和value。源码的细节方面会在下一篇文章中说明。现在我们先抛开细节部分,只看关键语句:

mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;

相信看到这大家都明白了原理了。但是它怎么查询呢?答案是二分查找。当插入时,根据key的hashcode()方法得到hash值,计算出在mArrays的index位置,然后利用二分查找找到对应的位置进行插入,当出现哈希冲突时,会在index的相邻位置插入。
总结一下,空间角度考虑,ArrayMap每存储一条信息,需要保存一个hash值,一个key值,一个value值。对比下HashMap 粗略的看,只是减少了一个指向下一个entity的指针。还有就是节省了一部分可见空间上的内存节省也不是特别明显。是不是这样呢?后面会验证。
时间效率上看,插入和查找的时候因为都用的二分法,查找的时候应该是没有hash查找快,插入的时候呢,如果顺序插入的话效率肯定高,但如果是随机插入,肯定会涉及到大量的数组搬移,数据量大,肯定不行,再想一下,如果是不凑巧,每次插入的hash值都比上一次的小,那就得次次搬移,效率一下就扛不住了的感脚。

3.SparseArray

 
Paste_Image.png

sparseArray相对来说就简单的多了,但是不要以为它可以取代前两种,sparseArray只能在key为int的时候才能使用,注意是int而不是Integer,这也是sparseArray效率提升的一个点,去掉了装箱的操作!。
因为key为int也就不需要什么hash值了,只要int值相等,那就是同一个对象,简单粗暴。插入和查找也是基于二分法,所以原理和Arraymap基本一致,这里就不多说了。
总结一下:空间上对比,与HashMap,去掉了Hash值的存储空间,没有next的指针占用,还有其他一些小的内存占用,看着节省了不少。
时间上对比:插入和查找的情形和Arraymap基本一致,可能存在大量的数组搬移。但是它避免了装箱的环节,不要小看装箱过程,还是很费时的。所以从源码上来看,效率谁快,就看数据量大小了。

好啦,说半天都是分析,下面来点实际的,用数据说话!

三.性能测试对比
我们从插入和查询两方面来比对试试看。

1.插入性能时间对比
测试代码:

long start = System.currentTimeMillis();
Map<Integer, String> hash = new HashMap<Integer, String>();
for (int i = 0; i < MAX; i++) {
hash.put(i, i+"");
}
long ts = System.currentTimeMillis() - start;

就贴这一段吧,其他两段代码无非就是把HashMap换掉,通过改变Max值就行对比。

 
Paste_Image.png

分析:从结果上来看,数据量小的时候,差异并不大(当然了,数据量小,时间基准小,内容太多,就不贴数据表了,确实差异不大),当数据量大于5000左右,SparseArray,最快,HashMap最慢,乍一看,好像SparseArray是最快的,但是要注意,这是顺序插入的。也就是SparseArray和Arraymap最理想的情况。

来个逆序插入的试试

long start = System.currentTimeMillis();
HashMap<Integer, String> hash = new HashMap<Integer, String>();
for (int i = 0; i < MAX; i++) {
hash.put(MAX-1-i, i+"");
}
long ts = System.currentTimeMillis() - start;
 
Paste_Image.png

分析:从结果上来看,果然,HashMap远超Arraymap和SparseArray,也前面分析一致。
当然了,数据量小的时候,例如1000以下,这点时间差异也是可以忽略的。

下面来看看空间对比:先说一下测试方法,因为测试内存,所以尤其要注意的一点,就是测试的过程不要发生GC,如果发生了GC,那数据就不准了,想了想,用了个比较简单的方法:

Runtime.getRuntime().totalMemory()//获取应用已经申请到的总的内存
Runtime.getRuntime().freeMemory()//获取应用内存的free部分

两个方法的差值就是应用已经使用的内存部分。

 
Paste_Image.png

值得注意的是当MAX值很大的时候,可能在代码执行过程发生GC,此时可以同时用Android Monitor的Memory窗口监视内存,没有发生gc的过程结果才有效。假设数据量比较大的时候,每测完一次手动GC一次,这样基本上每次都能测试成功;因为数据量也不是特别大,只有很少一部分情况测试过程会发生GC,所以也没有去进一步探究其他方式,比如设置虚拟机参数来延长GC时间,有空了可以搞一下。上数据:

 
Paste_Image.png

可见,SparseArray在内存占用方面的确要优于HashMap和ArrayMap不少,通过数据观察,大致节省30%左右,而ArrayMap的表现正如前面说的,优化作用有限,几乎和HashMap相同。

2.查找性能对比

long start = System.currentTimeMillis();
SparseArray<String> hash = new SparseArray<String>();
for (int i = 0; i < MAX; i++) {
hash.get(i);
}
long ts = System.currentTimeMillis() - start;
 
Paste_Image.png

发现SparseArray比HashMap要快,和前面假设的不符,二分查找难道比Hash快?
再一想,因为用这样的代码测试有点不公平,因为SparseArray没有装箱,HashMap有个装箱的过程,似乎不太公平。那么想个办法再来测试下,

ArrayList<IntEntity> intEntityList=new ArrayList<IntEntity>();
private void boxing(){
for(int i=0;i<MAX;i++){
IntEntity entity=new IntEntity();
entity.i1=i;
entity.i2=Integer.valueOf(i);
intEntityList.add(entity);
}
}
class IntEntity{
int i1;
Integer i2;
}

给HashMap和ArrayMap的时候给它提前装箱,这样似乎公平些。

long start = System.currentTimeMillis();
HashMap<Integer, String> hash = new HashMap<Integer, String>();
for (int i = 0; i < MAX; i++) {
// hash.get(i);
hash.get(intEntityList.get(i).i2);
}
long ts = System.currentTimeMillis() - start;
 
Paste_Image.png

果然结果不一样了,HashMap才是查询最快的,这才符合逻辑嘛,但是我们正常用的时候是不管装不装箱的,所以综合起来还是使用SparseArray效率最高。

扯了这么多,终于到了该总结的时候了。
四、总结
1.在数据量小的时候一般认为1000以下,当你的key为int的时候,使用SparseArray确实是一个很不错的选择,内存大概能节省30%,相比用HashMap,因为它key值不需要装箱,所以时间性能平均来看也优于HashMap,建议使用!
2.ArrayMap相对于SparseArray,特点就是key值类型不受限,任何情况下都可以取代HashMap,但是通过研究和测试发现,ArrayMap的内存节省并不明显,也就在10%左右,但是时间性能确是最差的,当然了,1000以内的数据量也无所谓了,加上它只有在API>=19才可以使用,个人建议没必要使用!还不如用HashMap放心。估计这也是为什么我们再new一个HashMap的时候google也没有提示让我们使用的原因吧。

【转】HashMap,ArrayMap,SparseArray源码分析及性能对比的更多相关文章

  1. 并发-HashMap和HashTable源码分析

    HashMap和HashTable源码分析 参考: https://blog.csdn.net/luanlouis/article/details/41576373 http://www.cnblog ...

  2. HashMap原理及源码分析

    HashMap 原理及源码分析 1. 存储结构 HashMap 内部是由 Node 类型的数组实现的.Node 包含着键值对,内部有四个字段,从 next 字段我们可以看出,Node 是一个链表.即数 ...

  3. 大数据学习--day14(String--StringBuffer--StringBuilder 源码分析、性能比较)

    String--StringBuffer--StringBuilder 源码分析.性能比较 站在优秀博客的肩上看问题:https://www.cnblogs.com/dolphin0520/p/377 ...

  4. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  5. JDK1.8 HashMap中put源码分析

    一.存储结构      在JDK1.8之前,HashMap采用桶+链表实现,本质就是采用数组+单向链表组合型的数据结构.它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置.Hash ...

  6. HashMap(三)之源码分析

    通过分析HashMap来学习源码,那么通过此过程我们要带着这几个问题去一起探索 为什么要学习源码 怎么去学习 0.1 为什么要学习源码 这个问题,直接给出结论,学习源码肯定是有好处的,比如: 学习优秀 ...

  7. HashMap:从源码分析到面试题

    1 HashMap简介 HashMap是实现map接口的一个重要实现类,在我们无论是日常还是面试,以及工作中都是一个经常用到角色.它的结构如下: 它的底层是用我们的哈希表和红黑树组成的.所以我们在学习 ...

  8. [nginx] nginx源码分析--SNI性能分析

    概念 我们已经知道什么是SNI,以及如何为用户配置SNI. [nginx] nginx使用SNI功能的方法 问题 通过观察配置文件,可以发现,针对每一个SSL/TLS链接, nginx都会动态的查找( ...

  9. 关于JDK1.8 HashMap扩容部分源码分析

    今天回顾hashmap源码的时候发现一个很有意思的地方,那就是jdk1.8在hashmap扩容上面的优化. 首先大家可能都知道,1.8比1.7多出了一个红黑树化的操作,当然在扩容的时候也要对红黑树进行 ...

随机推荐

  1. bootstrap学习大纲

    bootstrap 学习分三部分,分别是 css样式,css组件,js插件. 下面介绍三部分分别要学习的内容: 1.css样式:栅格系统,排版,代码,表格,表单,按钮,图片,辅助类,响应式工具. 2. ...

  2. multi_socket

    threading_test.py #threading #为什么在命令行可以执行,F5不能执行 #线程处理能导致同步问题 from socketserver import TCPServer,Thr ...

  3. BZOJ_4154_[Ipsc2015]Generating Synergy_KDTree

    BZOJ_4154_[Ipsc2015]Generating Synergy_KDTree Description 给定一棵以1为根的有根树,初始所有节点颜色为1,每次将距离节点a不超过l的a的子节点 ...

  4. SQL 电子书

    http://vdisk.weibo.com/search/?type=&sortby=default&keyword=SQL+Server&filetype=&pag ...

  5. codevs-2235

    2235 机票打折 题目描述 Description .输入机票原价(3到4位的正整数,单位:元),再输入机票打折率(小数点后最多一位数字).编程计算打折后机票的实际价格(单位:元.计算结果要将个位数 ...

  6. python 内置函数的补充 isinstance,issubclass, hasattr ,getattr, setattr, delattr,str,del 用法,以及元类

    isinstance   是 python中的内置函数 , isinstance()用来判断一个函数是不是一个类型 issubclass  是python 中的内置函数,  用来一个类A是不是另外一个 ...

  7. U3D开发性能优化笔记(待增加版本.x)

    http://blog.csdn.net/kaitiren/article/details/45071997 此总结由自己经验及网上收集整理优化内容 包括: .代码方面: .函数使用方面: .ui注意 ...

  8. CTP 下单返回错误: 没有报单权限 和字段错误需要注意的问题

    没有报单权限一般被认为期货公司没有开权限, 但是更多的问题是没有填写 BrokerId, InvestorId 下单字段错误注意一个容易忽略的地方: a. order 应该全部设为0, b. orde ...

  9. P4363 [九省联考2018]一双木棋chess(对抗搜索+记忆化搜索)

    传送门 这对抗搜索是个啥玩意儿…… 首先可以发现每一行的棋子数都不小于下一行,且局面可由每一行的棋子数唯一表示,那么用一个m+1进制数来表示当前局面,用longlong存,开map记忆化搜索 然后时间 ...

  10. 网络工程师HCNA认证学习笔记Day1

    企业网络 企业网络远程互联是广域网WAN互联,而非互联网Internet小型企业网络:一个路由器.交换机.AP大型企业网络:核心层.汇聚层.接入层.考虑可用性.稳定性.扩展性.安全性.可管理,冗余. ...