Java中常见的数据结构(二)——HashMap

这篇我们看看HashMap的实现
HashMap 由名字可知这是一个用Hash散列表实现的map集合
map集合的特点是 由Key 与 Value 组成的键值对 每个key对应自己的value 使得查找 只要计算key的hash值 就能得到value 的位置 ,而这种随机查找的特性显然是数组的特点 。
第一个我们就看到了

table

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

getNode(int hash,Object key)

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

putVal

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

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

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

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


ArrayMap的类注释

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


image.png

这里主要进行的是获取key对应的index值。
indexOf(key,hash)

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



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

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

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

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

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


remove流程的线程同步

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

你可能感兴趣的:(Java中常见的数据结构(二)——HashMap)