Java中常见的数据结构(二)——HashMap
2019-04-13 本文已影响0人
vvweilong
这篇我们看看HashMap的实现
HashMap 由名字可知这是一个用Hash散列表实现的map集合
map集合的特点是 由Key 与 Value 组成的键值对 每个key对应自己的value 使得查找 只要计算key的hash值 就能得到value 的位置 ,而这种随机查找的特性显然是数组的特点 。
第一个我们就看到了

这个数组就是map数据实体存放的位置了
还是 增删查 三个方法 首先看 查 get方法


我们看到 getNode() 是实际获取目标对象的方法,传入的参数有两个一个是 hash值 另一个是 key
hash值是通过hash(key)方法计算而来,为什么又传了key进来呢?
散列表有个问题就是碰撞问题,不同的key可能会产生相同的hash值,这就造成了碰撞,为了解决这个问题,在散列表相同位置会有一个链表来存储这些hash值相同的对象,为了在链表中找到目标key对应的值,所以要将key传进来。
这个过程在getNode()方法中很明显。
再看put 方法:


为了截图我折叠了一部分代码,首先看看put 的过程
首先我们看到一个 table 数组的判断和resize()方法,看来是对元素的实体集合进行了容量处理,毕竟是数组。
然后我们看到 用hash值 计算对应的数组索引来获取对象,如果为空则创建一个新的结点对象保存到数组中 ,如果不为空

我们看有三种情况的处理
首先判断了目标位置结点与新加入结点是否为同一个判断方法是 hash值相同,并且key相同
然后判断了当前位置的结点是否是树形结点如果是 则进行树形结点的put操作。
最后是添加到链表尾部的情况。
(结尾执行的afterNodeAccess(e)与afterNodeInsertion(e)都是空实现,在用链表实现的hashmap中有具体的实现操作。)
remove()方法与 put() 方法过程类似,都是通过hash找到数组的索引然后查询链表找到对象 。下面我们看一下resize()方法;

我们看到 在一般情况下 newCap=oldCap<<1 也就是2倍关系 也就是说正常情况,每次扩容都是 2×oldCap ;
我们还看到两个变量 threshold 与 loadFactor
threshold 的注释说 这个下一次进行resize的容量数值 于是我们在putVal方法中看到这样一句

在Android SDK中还有另一种类似数据 ArrayMap

说明ArrayMap是安卓设计的一种更高效利用内存的HashMap数据结构
采用两个数组来实现map结构的Key-Value对应关系,一个存储hashcode 一个存储 key/value pair 对象,还是看一下具体代码 以put方法为切入口
先看第一部分

这里主要进行的是获取key对应的index值。

首先一个二分查找在hash值的数组中查找目标hash 的索引
然后通过索引在mArray中找到对应的key ,返回目标key的索引
最后如果没有找到目标hash值说明是个新的key 则返回一个新key应该放置的index

执行到这部分说明是个新加入的key 和value ,首先要对容量进行扩容检查 ,扩容之后

将新的key放在mHashes数组的对应位置,将值翻入mArray的对应位置。
我们再来看看 线程安全的HashMap HashTable

可以看到 是在方法上加了 synchronized 关键字来实现线程同步,其他的功能实现与HashMap类似。
接下来是 concurrent包下的HashMap

方法前并没有 synchronized 关键字,那是在哪里进行了线程同步呢 ?

这个f 是在执行第一个else if时进行了赋值 tabAt()也就是key对应散列表的列表表头对他进行了加锁处理。
而remove操作对应的是replaceNode方法

同样也是在找到key对应的节点后 进行锁处理。