几种 Map 内部存储方式的介绍( 以 Java 为例讲解 )

原创 2016年01月31日 13:16:25

Map,即映射,也称为 键值对,有一个 Key, 一个 Value 。

比如 Groovy 语言中,  def  map = ['name' : 'liudehua', 'age' : 50 ] ,则 map[ 'name' ]  的值是 'liudehua'。 

那么 Map 内部存储是怎么实现的呢?   下面慢慢讲解.

一、 使用 拉链式存储

这个以 Java 中的 HashMap 为例进行讲解。   HashMap 的内部有个数组 Entry[]  table, 这个数组就是存放数据的。 

Entry 类的定义大致是 :

class Entry {

Object  key

Object  value

Entry next;

}

所以, Entry[]  table  的每个元素都是一个链表,即 HashMap 的内部存储是 数组 + 链表,即拉链式存储。

当往 HaspMap 中 put(key,  value) 数据时,先进行  key.hashCode()  &  (table.length() - 1) ,得到一个小于 table.length() 的值, 称为 index, 则这个新的 Entry 就属于 table[index] 这个链表了 ( 如果链表还不存在,则把这个新的 Entry 作为链表的头部了 );  然后开始从前往后遍历  table[index] 这个链表,如果 key.equals( entry.key ), 那么表示这个 key 已经有了旧值,则替换 value 的值即可;

否则,把这个新的 Entry 插入到 table[index] 链表的最前面.

以上就是 HashMap 的存储方式介绍了, 而且可以知道,作为 HashMap 的 Key, 它的 hashCode() 和 equals() 方法都被使用了

二、数组存储

1.分散的数组存储

这个以 ThreadLocal  和 ThreadLocal.Values  类为例进行讲解。 Values 类里面有两个变量, Object[]  table, 和 mask , table 就是存储数据的数组了,table 的长度是 2 的倍数 ,  mask 的值就是  table.length - 1 ;  这一点和 HashMap 的内部存储很像。  不过 table 中的每个元素就不是链表了。

当往  Values 中进行 put(key, value) 时,先进行 key.hashCode & mask ,得到一个小于 table.length 的值,称为 index (与上面的 HashMap 好像,哈哈), 然后去检查 table[index] 的值,如果不存在,则在 table[index] 处放入 key, table[index + 1] 处放入 value; 如果已经存在了,且 key.equals( oldKey ) 不成立,即发生了冲突,那么 index = index + 2 ( 此处是 + 2,因为 key ,value 两个是挨着放的,一个元素占俩坑 ) ; 往下一地方找找,如果再冲突,再找,直到找到一个可插入的地方,把 table[index] = key, table[index + 1] = value;  

有两处需要注意:

key.hashCode() 必须是 2 的倍数, 否则 index 的值有可能为奇数,如此就可能发生冲突了.  可参考 ThreadLocal.hash 这个成员变量

table 内部的数据是分散着存储的.


2.连续的数组存储

这个以 Android 中新增的 ArrayMap 为例进行分析( 据说没 ArrayMap 是用来替换 HashMap 的哈 ),  ArrayMap 中有两个主要变量, int[]  mHashes, Object[]  mArrays. 

mHashes 主要是存放 key 的 hash 值的, mArrays 是用来存放数据的,它也是奇数存放 key ,偶数存放 value , key 和 value 顺序排列( 这个和 TheadLocal.value 中的 table 存储方式很像 )。  mArrays 的长度是 mHashes 的 2 倍,毕竟 mArrays 是 key, value 都要存嘛~

mHashes 中存放 key 的 hash 值,是从小到大排列的,如果有多个 key 的 hash 值有一样的,那么就挨着排列

当往 ArrayMap 中进行 put(key, value) 时,先 int hash = key.hashCode, 然后通过二分查找在 mHashes 中查找 hash 的位置( 如果里面有,就返回,如果无,就先找到最接近的位置,然后进行取反操作并返回 )  index,如果 index > 0 ,那么再去 mArrays 中 2 * index 处获取 key 值,比对两个 key 是否 equals(), 如果不相等,那么再分别向上、向下查找附近相同 hash 值的 key ,看是否有 equals() 的 key, 若有,则替换,若无,则插入;    如果 index < 0 ,表示之前没有相等 hash 的 key 插入过,那么 index = ~index( 再次取反,就是个正数了,代办要插入的位置), 再在 mHashes 的 index 处插入 hash, 在 mArrays 的 2 * index 处插入 key, 在 (2 * index ) + 1 处,插入 value . 

注意:

mHashes 和 mArrays 在插入新数据时,都需要把插入位置后面的数据向后移动一个单位,这个对于频繁插入、删除的动作来说消耗比较大.

key 的 hash 大小决定了插入的顺序


3.以数字为 key 的数组存储

这类的 map 比较特殊,key 是数字类型。  这个以 Android 中新增的 SparseArray 进行分析。 SparseArray 中有两个主要变量, int[]  mKeys  和  Object[]  mValues , mKeys 是存放 key 的,是个有序数组,从小到大排列;  mValues 是与 mKeys 一一对应的 value 值集合.   mKeys 和 mValues 的长度是相等的。

当往  SparseArray 中 put(key, value) 时,先用二分查找在 mKeys 中查找 key 所在的位置 (如果找到,返回; 如果没有找到,则找到它应该插入的位置,取反并返回) ,记为 index, index > 0 ,则直接在 mValues[index] 处替换 value; 如果 index < 0 ,则 index = ~index, 即取反, 然后在 mKeys 的 index 处插入 key , 在 mValues[index] 处插入 value ,之前的数据自 index 处后移一个单位。

注意:

mKeys 和 mArrays 的数据插入时,都是要进行数据移动的,对频繁插入、删除的 map 来说消耗很大.


最后了,对它们的优缺点做些对比。

HashMap : 内存占用较大,增、删的效率较高,改、查的效率一般

ThreadLocal.Values :  内存占用一般,当数据量比较小时,增删改查的效率高;数据量大时,增删改查效率一般

ArrayMap: 内存占用较小,改、查的效率高,增、删的效率较低

SparseArray : 内存占用较小,改、查的效率高,增、删的效率低,且主键是数字


最后,我们不评判哪种存储方式好,一切都要以实际情况实际分析,找出最符合的那种存储,哈哈~

List、Map、Set按存储方式说说都是怎么存储的?

collection、set、list、map这些都是java中常见的集合,对于初学者来讲这些是非常容易混淆的,首先让我们从整体上对这些集合有个大致的了解然后我们再进行区分。   1.colle...
  • Mr_linjw
  • Mr_linjw
  • 2016-05-06 23:43:29
  • 5442

Map中有序存储

我们都知道,Map中是以key-value形式存储的,通常java.util.Map是无序的,例如:   Map map=new HashMap();   map.put("yi","壹");...
  • kingo0
  • kingo0
  • 2016-05-26 14:50:09
  • 3122

hashMap存储原理

http://blog.csdn.net/vking_wang/article/details/14166593
  • lili_guo
  • lili_guo
  • 2016-07-18 10:23:03
  • 4711

Map的存储方式

Map存储时类似于栈,先存的在底部,而不是类似于集合的从0开始,所以当需要根据位置或者FOR循环根据I字段获取值时需要注意这一点...
  • ForeverShine
  • ForeverShine
  • 2015-04-15 11:04:07
  • 408

java中在map中存取值操作

在java中map是使用键值对的形式存在的这与数组非常的相似。Map是一个接口它当中包括:HashTable,HashMap,TreeMap等实现类! 对map操作的方法有以下几种,通过这些方法将...
  • jie1031066280
  • jie1031066280
  • 2014-02-28 16:07:57
  • 12444

List接口及其实现类、Map接口及其实现类

List接口及其实现类:特点:其元素以线性方式存储;          元素可重复;          元素有序;          可以对元素的位置精确控制;实现类:ArrayList       ...
  • u012843873
  • u012843873
  • 2016-07-05 10:12:11
  • 2968

Java中常见数据结构:list与map -底层如何实现

1:集合 2 Collection(单列集合) 3 List(有序,可重复) 4 ArrayList 5 ...
  • xy2953396112
  • xy2953396112
  • 2017-02-06 12:07:03
  • 8196

map存储数据

使用map的简单实例如下: demo1:Java package encapsulation; /** * Created by 黄天柱 on 2016/11/13. */ impor...
  • huangtianzhu123
  • huangtianzhu123
  • 2016-11-13 00:55:40
  • 92

关于HaspMap存放顺序的解决问题

最近在做一个项目时,有一个程序需要通过HaspMap来传递参数,而且接收参数的方法对Map中的参数顺序敏感。 所以一开始调试的时候没有注意到HashMap是无序的,就是和你set的顺序无关的,所...
  • u011285162
  • u011285162
  • 2014-02-15 11:52:48
  • 1841

Map有序存储数据

我们知道 Map存储数据的时候是无序的。而有的时候,我们按照自己的顺序进行排序。譬如:你查询出一个集合数据,往map里塞数据的时候,想要按照自己查询时的数据顺序进行排序。那么我们就不能用常规的map来...
  • moyanxuan_1993_2_24
  • moyanxuan_1993_2_24
  • 2015-08-27 14:22:44
  • 37816
收藏助手
不良信息举报
您举报文章:几种 Map 内部存储方式的介绍( 以 Java 为例讲解 )
举报原因:
原因补充:

(最多只允许输入30个字)