HashMap与ArrayMap(和SparseArray)的比较与选择

2017年12月26日 06:04:38 阅读数:61 标签: androidjavahashmaparraymap数据结构 更多

个人分类: AndroidJava
https://blog.csdn.net/shangsxb/article/details/78898323
 
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shangsxb/article/details/78898323

HashMap之外的Map实现

HashMap应该是java中使用最多的Map实现了,ArrayMap为Android SDK提供的另一个Map接口的实现。
SparseArray的实现思路和ArrayMap是一致的,所以捎上说一下

补充说明

ArrayMap在v4包中有兼容的实现,需要兼容低版本不要导错包
android.util.ArrayMap
android.support.v4.util.ArrayMap

HashMap的实现


HashMap是通过数组+链表的形式存储数据,内部有一个名为table的Node类型的数组用以存放数据,每一个Node都可以向后构成一个单向链表,用于在hash重复而key不相同时保存新的键值对
Node类的结构:

static class Node<K,V> implements Entry<K,V>{
final int hash; //key对象的hashCode值
final K key; //key对象
V value; //value对象
Node<K,V> next; //指向下一个Node对象的引用
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

HashMap通过Key对象的hashCode方法返回int型hash值,经过系列计算在数组中的下标。
下面分析一下hash->index的转换过程

Key对象->table下标转换

第一步,调用key对象的hashCode方法获取int值

通过Key对象的hashCode方法,获取int型的Hash值,如果key对象为null则为0。
这里就涉及到了HashMap和HashTable的一个区别:HashMap允许null Key而HashTable不允许。这是因为HashTable直接调用了Key对象的hashCode方法而缺少了null时的判断。
将”HashMap通过key对象的hashCode方法获取的int型hash值”起名为hash,后面提到hash均为此。
在Key对象为null时直接赋值为0进行第三步,不为0时多则进行第二步对hash修正

第二步,对hash修正

hash = hash ^ (hash>>>16)
  • 1

将hash和自己的高16位xor。为什么多了这一步的操作的原因在下一步操作中说。

第三步,hash->数组下标转换

如何保证计算出来的下标一定在数组长度范围内?最简单的方法就是hash%table.length,取余的结果一定在[0,table.length)区间内,这也是HashTable使用的方法。
但是计算机中除法和取余运算是最慢的,而位运算是最快的,所以HashMap使用位运算来转换,这也是为什么HashMap的table长度一定是2n的原因。
我们知道2n对应的二进制是1后面n个零,以HashMap的table默认初始长度16为例,此时数组长度24对应二进制是
10000
减1可以得到
01111
index = 01111&hash,位与得到的结果index一定<=01111,也就是一定在[0,table.length)区间内。
HashMap用位运算实现了和HashTable取余同样的效果(注意这里是等效不等价的),这也是除了同步锁以外HashMap比HashTable效率高的另外一个原因。HashTable中hash值到index转换是

index = (hash&0x7FFFFFFF)%table.length  
  • 1

使用符号位后和table.length取余,HashMap的位运算自然要比HashTable的取余运算效率高。

对第二步hash修正的说明

说回第二步中对hash的修正,hashCode方法返回的原始hash值存在一种可能,大部分1都在高位,此时数组又比较小的话,直接用原始hash值和table.length-1位与可能会丢掉太多1,导致hash大量碰撞,所以将高16位无符号右移并与低16位异或,这是为了让高16位在数组长度比较小的情况下也能参与计算,降低hash碰撞概率

存取

获取到数组下标后可以获取对应位置的数组元素了,如果为空则表示不存在,可以直接存放新值。如果不为空就是Node链表的头节点,此时需要遍历Node对象,通过Key对象的equals检查是否相符。增删特性和链表相同不再细说。

ArrayMap的实现


ArrayMap内部通过两个数组保存映射关系,其中int[] mHashes按大小顺序保存Key对象hashCode值,Object[]
mArray按mHashes的顺序y用相邻位置保存Key对象和Value对象。可以发现ArrayMap使用一个数组同时保存key和value对象,所以mArray长度一定是mHashes长度的2倍,通过两个数组的初始化代码也能看出

mHashes = new int[size];
mArray = new Object[size<<1];
  • 1
  • 2

ArrayMap相对于HashMap,无需为每个键值对创建Node对象,并且在数组中连续存放,这就是为什么ArrayMap相对HashMap要节省空间。
ArrayMap也是通过Key对象的hashCode方法返回int型hash值,通过一系列计算获取对应在数组中的下标。下面分析ArrayMap中hash->index的转换过程

Key对象->mArray下标转换

第一步,调用key对象的hashCode方法获取int值

通过Key对象的hashCode方法,获取int型的Hash值,如果key对象为null则为0。这里和HashMap是完全一样的。
和之前一样,将”key对象的hashCode方法获取的int型hash值“起名为hash

第二步,通过二分法查找获取hash在mHashes数组中的下标index

mHashes中的hash值是按照有小到大的顺序(自然排序)连续摆放的,通过binarySearch获取对应hash的下标index,去mArray中查找键值对

第三步,mHashes下标查找mArray键值对

mHashes中的index*2即为mArray中的Key下标,index*2+1为Value的下标。由于存在hash碰撞的情况,而二分法查找到下标可能是多个连续相同hash值中的任意一个,所以此时需要用equals比对对命中的Key对象是否相符,不相符时,从当前index先向后再向前遍历所有相同hash值。

存取

由于是用数组中连续位置存放的,数组各元素中没有空余位置,空间占用更优。最好的情况时在最尾部增删,如果在中间增删则需要移动数组元素,这里和ArrayList原理相同不再细说。
index是通过二分法查找或者向后遍历获取的,插入时可以直接使用。

SparseArray

SparseArray和ArrayMap的实现原理是完全一样的,都是通过二分法查找Key对象在Key数组中的下标来定位Value,SparseArray相比ArrayMap进一步优化空间提高性能。
SparseArray的目的是专门针对基本类型做优化,Key只能是可排序的基本类型,比如int型key的SparseArray,long型key的LongSparseArray,对于Value,除了泛型Value,对每种基本类型都有单独的实现,比如SparseBooleanArray,SparseLongArray等等。

  1. 无需包装
    直接使用基本类型值,不需要包装成对象。
  2. 无需hash,无需比对Key对象
    直接使用基本类型值排序索引和判断相等,无碰撞,无需调用hashCode方法,无需equals比较。
  3. 更小的内部数组
    相比于ArrayMap,无需单独的hash排序数组,内部只需等长的两个数组分别存放Key和Value
  4. 延迟删除
    对于移除操作,SparseArray并不是在每次remove操作直接移动数组元素,而是用一个删除标记将对应key的value标记为已删除,并标记需要回收,等待下次添加、扩容等需要移动数组元素的地方统一操作,进一步提升性能。
  5. 有序
    所有键值对均是按照基本类型key的自然排序,支持下标访问(keyAt方法和valueAt方法),迭代遍历和数组相同

总结

1. 空间

HashMap的内部数组长度必须是2n,需要大一些降低碰撞概率(可以通过负载因子调节),数组元素是跳跃的,需要为键值对创建Node对象在碰撞时拉链。
ArrayMap则是通过牺牲性能换取空间,没有2n限制,数组长度无需太长,size范围内没有闲置位置,无需为键值对创建Node对象。

2. 查找

HashMap在元素非常多时性能要高于ArrayMap。
HashMap直接通过hash值位运算计算出下标,ArrayMap需要通过二分法查找;hash碰撞时HashMap只需遍历链表,ArrayMap需要分别向后向前遍历数组。

3. 增删

这个几乎就是LinkedList和ArrayList的区别了

4. 扩容

这个是ArrayMap优于HashMap的地方。
HashMap的下标位置是和数组容量相关的,带来一个问题,每次数组容量改变都需要重新计算所有键值对的下标,也就是rehash。而ArrayMap则没有这个问题,只需要创建一个更大的数组,用System.arrayCopy把元素复制过去。

5. 遍历

HashMap需要遍历数组和数组中的每一个单向链表,并且数组元素是跳跃的;ArrayMap则只用遍历一个连续的mArray数组即可

6.选择

可以看出ArrayMap适合数量不多、对内存敏感、频繁扩容的地方,而在元素比较多时HashMap更优

HashMap与ArrayMap(和SparseArray)的比较与选择的更多相关文章

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

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

  2. HashMap、ArrayMap、SparseArray分析比较

    http://blog.csdn.net/chen_lifeng/article/details/52057427

  3. <转载>Android性能优化之HashMap,ArrayMap和SparseArray

    本篇博客来自于转载,打开原文地址已经失效,在此就不贴出原文地址了,如原作者看到请私信我可用地址,保护原创,人人有责.   Android开发者都知道Lint在我们使用HashMap的时候会给出警告—— ...

  4. 用hashMAP或ArrayList解决recylerView中checkbox的选择错乱问题。

    //这个监听一定要放在checkbox初始化的方法之前,否则无效.是因为滑动的时侯会重新给checkbox赋值造成的.holder.cbFileSel.setOnCheckedChangeListen ...

  5. Android应用性能优化(转)

    人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯(帧率为24fps),用手机当然也需要感知屏幕操作的连贯性(尤其是动画过度),所以Android索性就把达到这 ...

  6. 转——Android应用开发性能优化完全分析

    [工匠若水 http://blog.csdn.net/yanbober 转载请注明出处.] 1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉 ...

  7. Android 应用开发性能优化完全分析

    1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...

  8. 【转】Android应用开发性能优化完全分析

    http://blog.csdn.net/yanbober/article/details/48394201 1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关 ...

  9. Android应用开发性能优化完全分析

    1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...

随机推荐

  1. 删除vi编辑产生的.swp文件(linux编辑文件没有退出时直接关闭程序产生的临时文件)

    关于swp文件 使用vi,经常可以看到swp这个文件,那这个文件是怎么产生的呢,当你打开一个文件,vi就会生成这么一个.(filename)swp文件以备不测(不测下面讨论),如果你正常退出,那么这个 ...

  2. devOps开发(Web API 实例)dotnet core 和 Azure PaaS服务

    使用 dotnet core 和 Azure PaaS服务进行devOps开发(Web API 实例) 作者:陈希章 发表于 2017年12月19日 引子 这一篇文章将用一个完整的实例,给大家介绍如何 ...

  3. NET Core写了一个轻量级的Interception框架[开源]

    NET Core写了一个轻量级的Interception框架[开源] ASP.NET Core具有一个以ServiceCollection和ServiceProvider为核心的依赖注入框架,虽然这只 ...

  4. 四则运算二(java web)

    最近我和我的小伙伴yaoyali结成对子,共同写网页版的四则运算.虽然现在还没弄好,但是比起上次用纯java写的四则运算有了很大改进. 一.存放四则运算题目和答案的类 package com.jaov ...

  5. 如何减小SQL 的物理读,。

    1.dev time:1226 1个跑批 db_file_multiblock_read_count =128 60.05 (mins) 26-Dec-17 16:00:20 ~ 26-Dec-17 ...

  6. 《深入理解java虚拟机》笔记(6)内存分配与回收策略

    一.垃圾回收日志说明 [GC[DefNew: 7307K->494K(9216K), 0.0043710 secs] 7307K->6638K(19456K), 0.0044894 sec ...

  7. var type = $('#<%=DropDownListRateType.ClientID %>').val();DropDownListRateType.ClientID是什么意思

    <%=DropDownListRateType.ClientID %>这个是C#绑定服务器控件在客户端ID, 比如你的DropDownListRateType你定义一个id,如果你用了模板 ...

  8. java实现xml文件读取并保存到对象

    首先浅聊一下解析xml的四种方式: 1.DOM方式:有缺点但是这个缺点却也是他的优点.下面详细介绍: 以树形的层次结构组织节点或信息片断集合,可以获得同一个文档中的多处不同数据.使用起来简单. 优点是 ...

  9. iOS Runtime常用方法整理

    关于runtime的学习网上有很多博客,在学习之前也查过很多资料,觉得南峰子老师博客中对 runtime 的讲解挺详细的,博客地址:http://southpeak.github.io/categor ...

  10. linux touch和vi建立的文件是什么文件类型的

    都是acsii类型的文本文档,但是也可以建立其他格式的,比如vi newFile.c(c是c语言动态链接库格式)