美文网首页算法与数据结构
Java中常见的数据结构(二)——HashMap

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

作者: vvweilong | 来源:发表于2019-04-13 18:44 被阅读0次

    这篇我们看看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

        本文链接:https://www.haomeiwen.com/subject/micrwqtx.html