zoukankan      html  css  js  c++  java
  • HashMap


    HashMap可以接受null键值和值,而Hashtable则不能;HashMap是非synchronized;HashMap很快;以及HashMap储存的是键值对
    HashMap是基于hashing的原理,使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,先对键调用hashCode()方法,返回的int类型的hashCode值用于找到bucket位置来储存Entry对象。”(HashMap是在bucket中储存键对象和值对象,作为Map.Entry)
    当两个对象的hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中

    在数据结构中有一种称为哈希表的数据结构,它实际上是数组的推广。如果有一个数组,要最有效的查找某个元素的位置,如果存储空间足够大,那么可以对每个元素和内存中的某个地址对应起来,然后把每个元素的地址用一个数组(这个数组也称为哈希表)存储起来,然后通过数组下标就可以直接找到某个元素了。这种方法术语叫做直接寻址法。这种方法的关键是要把每个元素和某个地址对应起来,所以如果当一组数据的取值范围很大的时候,而地址的空间又有限,那么必然会有多个映射到同一个地址,术语上称为哈希冲突

    HashMap采用链地址法解决哈希冲突,多线程访问哈希表的位置并修改映射关系的时候,后执行的线程会覆盖先执行线程的修改,所以不是线程安全的

    Hashtable采用synchronized关键字解决了并发访问的安全性问题但是效率较低

    问:

    为啥不安全
        1 hashmap的put方法调用addEntry()方法,假如A线程和B线程同时对同一个数组位置调用addEntry,两个线程会同一时间片同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写 入操作造成A的写入操作丢失
       2 HashMap的get操作可能因为resize而引起死循环
      HashMap的扩容机制就是重新申请一个容量是当前的2倍的桶数组,然后将原先的记录逐个重新映射到新的桶里面,然后将原先的桶逐个置为null使得引用失效,
      线程thread1执行到了transfer方法的Entry next = e.next这一句,然后时间片用完了,此时的e = [1,A], next = [2,B]。线程thread2被调度执行并且扩容 此时 [2,B]的next 为[1,A]在取链表的时候从是从尾部开始取,形成了环形链表,    如果get的key的桶索引会陷入死循环

    1、Entry是什么
         Entry一个单向链表 Entry实现了Map.Entry接口

    2、如果两个键的hashcode相同,如何获取值对象?
         当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后获取值对象。
         找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象

    3、hashmap怎么解决碰撞问题
         HashMap本质就是数组+链表结构
         HashMap里面没有出现hash冲突时,没有形成单链表时,hashmap查找元素很快,get()方法能够直接定位到元素,但是出现单链表后,单个bucket 里存储的不是一个                Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最        早放入该 bucket 中),那系统必须循环到最后才能找到该元素。

        简单理解:根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面  ,通过数组下标取出链表,遍历链表用           equals取出对应key的value

    4、什么是单向链表
         链表是由一些节点构成的,这些节点之间由指针连接,形成了一个链式结构。最基本的链表节点只需要存储当前节点的值,和一个指向下一节点的指针。由这种只存储      下一节点地址的链表节点构成的链表被称为单向链表。

         Entry<k,v>可以看出该类是用来存放键值对的,并且通过next指向下一个实例实现链表结构。
        在HashMap中定义了一个Entry类型的数组(Entry[ ] table ),当要put key-value时,先通过key计算hashcode作为该数组的下标,然后将实例化的Entry<>置于此位         置。若发生冲突(不同的key对应相同的下标)则通过Entry中的next实现链接。
        所以HashMap中的Entry<k,v>类型是以链表的形式存放键值对的。

    5、如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
         默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,        来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

    5、重新调整HashMap大小存在什么问题?
         当多线程的情况下,可能产生条件竞争。
         当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整 大小。在调整大小的过程中     存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在
        链表的尾部,而是放在头部,这是为了避免尾部遍历。如果条件竞争发生了,那么就死循环了

    -----------------------------------------------------------------------------------------------------------------

    总结
    当往hashmap中put元素的时候,先根据key的hash值得到这个元素在数组中的位置(即下标),然后就可以把这个元素放到对应的位置中了。如果这个元素所在的位子上已经存放有其他元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。从hashmap中get元素时,首先计算key的hashcode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

    HashMap的工作原理
    HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。

    当两个不同的键对象的hashcode相同时会发生 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。

    HashMap的好处非常多,在电子商务的应用中使用HashMap作为缓存。出于性能的考虑,会经常用到HashMap和ConcurrentHashMap。

    群交流(262200309)
  • 相关阅读:
    JDBC原理及常见错误分析
    response,session,cookie
    Activity LifeCycle (安卓应用的运行机制)
    简单的接口取数据渲染到图表
    图表里面双重下拉框进行判断
    用js方式取得接口里面json数据的key和value值
    一个div多个图表共用一个图例
    一个页面多图表展示(四个div的方式)
    vue组件之子组件和父组件
    根据判断对颜色进行改变
  • 原文地址:https://www.cnblogs.com/webster1/p/8436388.html
Copyright © 2011-2022 走看看