HashMap与ArrayMap(和SparseArray)的比较与选择
HashMap与ArrayMap(和SparseArray)的比较与选择
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等等。
- 无需包装
直接使用基本类型值,不需要包装成对象。 - 无需hash,无需比对Key对象
直接使用基本类型值排序索引和判断相等,无碰撞,无需调用hashCode方法,无需equals比较。 - 更小的内部数组
相比于ArrayMap,无需单独的hash排序数组,内部只需等长的两个数组分别存放Key和Value - 延迟删除
对于移除操作,SparseArray并不是在每次remove操作直接移动数组元素,而是用一个删除标记将对应key的value标记为已删除,并标记需要回收,等待下次添加、扩容等需要移动数组元素的地方统一操作,进一步提升性能。 - 有序
所有键值对均是按照基本类型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)的比较与选择的更多相关文章
- 【转】HashMap,ArrayMap,SparseArray源码分析及性能对比
HashMap,ArrayMap,SparseArray源码分析及性能对比 jjlanbupt 关注 2016.06.03 20:19* 字数 2165 阅读 7967评论 13喜欢 43 Array ...
- HashMap、ArrayMap、SparseArray分析比较
http://blog.csdn.net/chen_lifeng/article/details/52057427
- <转载>Android性能优化之HashMap,ArrayMap和SparseArray
本篇博客来自于转载,打开原文地址已经失效,在此就不贴出原文地址了,如原作者看到请私信我可用地址,保护原创,人人有责. Android开发者都知道Lint在我们使用HashMap的时候会给出警告—— ...
- 用hashMAP或ArrayList解决recylerView中checkbox的选择错乱问题。
//这个监听一定要放在checkbox初始化的方法之前,否则无效.是因为滑动的时侯会重新给checkbox赋值造成的.holder.cbFileSel.setOnCheckedChangeListen ...
- Android应用性能优化(转)
人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯(帧率为24fps),用手机当然也需要感知屏幕操作的连贯性(尤其是动画过度),所以Android索性就把达到这 ...
- 转——Android应用开发性能优化完全分析
[工匠若水 http://blog.csdn.net/yanbober 转载请注明出处.] 1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉 ...
- Android 应用开发性能优化完全分析
1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...
- 【转】Android应用开发性能优化完全分析
http://blog.csdn.net/yanbober/article/details/48394201 1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关 ...
- Android应用开发性能优化完全分析
1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...
随机推荐
- 聊聊 Laravel 5.5 的 「自动发现」
ThinkSNS是什么? ThinkSNS(简称TS),一款全平台综合性社交系统,目前最新版本为ThinkSNS+.ThinkSNS V4 ThinkSNS[简]. 看了Taylor Otwell发表 ...
- Django之extra
extra过滤 extra extra(select=None, where=None, params=None, tables=None, order_by=None, select_params= ...
- NET Core 开发环境
NET Core 开发环境 最近,一直在往.Net Core上迁移,随着工作的深入,发现.Net Core比.Net Framework好玩多了.不过目前还在windows下开发,虽然VisualSt ...
- postgresql修改数据库名
alter database abc rename to cba;
- LWIP学习之流程架构
一 STM32F107的网络接口配置:#include "stm32_eth.h" 1.1 打开网口时钟,响应IO配置.NVIC中断:通过调用Ethernet_Configurat ...
- D. Blocks 数学题
Panda has received an assignment of painting a line of blocks. Since Panda is such an intelligent bo ...
- (转)Inode详解
Inode详解 原文:http://www.cnblogs.com/adforce//p/3522433.html 一.inode是什么 理解inode,要从文件储存说起. 文件储存在硬盘上,硬盘的 ...
- echarts Hello world 入门
<!DOCTYPE html> <html> <head> <title></title> <script type="te ...
- android应用开发全程实录-你有多熟悉listview?
今天给大家带来<android应用开发全程实录>中关于listview和adatper中的部分.包括listview的基本使用,listview的优化等. 我们经常会在应用程序中使用列表的 ...
- rest_framework序列化组件
一.Django自带的序列化组件 ==>对象序列化成json格式的字符串 from django.core import serializers from django.core import ...