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. SSH框架中hibernate 出现 user is not mapped 问题

    SSH框架中hibernate 出现 user is not mapped 问题      在做SSH框架整合时,在进行DAO操作时.这里就只调用了chekUser()方法.运行时报  user is ...

  2. Linux终端程序用c语言实现改变输出的字的颜色

    颜色代码: 格式: echo "\033[字背景颜色;字体颜色m字符串\033[0m" 例如: echo "\033[41;36m something here \033 ...

  3. Array 对象

    Array的对象用于在单个的变量中存储多个值. constructor 返回对创建此对象的数组函数的引用. demo: let arr=[];  arr.constructor==Array let ...

  4. TypeScript完全解读(26课时)_8.ES6精讲-ES6中的类(进阶)

    8.TypeScript完全解读-ES6精讲-类(进阶) 在index.ts内引入 Food创建的实例赋值给Vegetabled这个原型对象,这样使用Vegetables创建实例的时候,就能继承到Fo ...

  5. hql实现对表的某几个(部分)字段查询

    如何利用hql实现对表的部分字段查询 假如,我们有一张person表,对应实体类Person,表中有字段name,age,sex,address 哪我们如何来实现全部和部份字段的查询呢? hql的写法 ...

  6. 洛谷 - P3935 - Calculating - 整除分块

    https://www.luogu.org/fe/problem/P3935 求: \(F(n)=\sum\limits_{i=1}^{n}d(i)\) 枚举因子\(d\),每个因子\(d\)都给其倍 ...

  7. c# 中的 protected internal 如何在 vc.net 中实现

    c# 中有 protected internal 的复合访问属性, 保证assembly内部访问,以及外部的派生类访问 vc.net 中无法直接写上 protected internal, 其对应的写 ...

  8. [Xcode 实际操作]九、实用进阶-(15)屏幕截屏:截取当前屏幕上的显示内容

    目录:[Swift]Xcode实际操作 本文将演示如何截取屏幕画面,并将截取图片,存入系统相册. 在项目导航区,打开视图控制器的代码文件[ViewController.swift] import UI ...

  9. ASPNET-ASPNETCORE 认证

    话题背景 关于认证我的个人理解是,验证信息的合法性.在我们生活当中,比如门禁,你想进入一个有相对安全措施的小区或者大楼,你需要向保安或者门禁系统提供你的身份信息证明,只有确定你是小区业主,才可以进来, ...

  10. Validation(4)-临时

    使用Hibernate-Validator优雅的校验参数 2019年01月01日 13:17:31 余生之君 阅读数:337    版权声明:本文为博主原创文章,未经博主允许不得转载. https:/ ...