zoukankan      html  css  js  c++  java
  • HashMap底层数据结构?jdk1.8算法优化,hash冲突,扩容等问题

    面试必备系列不会长篇理论求证,直接上答案,仅供参考,不喜勿喷。

    1、能说说HashMap的底层原理吗?

    HashMap<String,String> map = new HashMap<String,String>(); 

    map.put(“key”,”value”); 

    [<key1,value1>,<key2,value2>,<key3,value3>] 

    HashMap底层实现是数组+链表,用来存储<key,value>形式的数据,当我们调用put(key,value)时,首先会通过hash(key) 来获取key的hash值,hash值对数组长度进行取模运算,定位到数组的一个存储位置 bucket,如果bucket没有发生冲突的话则直接放入数组,发生冲突的话则以链表的形式存储,jdk1.8之后引入了红黑树,链表的长度超过8之后会使用红黑树,小于6之后则又转换回来。

    如下2、3条皆为第1条的补充,预防面试官细问。

    2、jdk1.8中对hash算法和寻址算法的优化

    jdk1.8中对hash算法进行了优化,之前在对key进行hash(key)计算时,采用的是取模运算,即第1条提到的,而优化后采用的是寻址算法,即:(n-1) & hash 『n为数组长度』

    为什么要使用寻址算法呢?首先 「hash & (n-1)」 效果跟 hash 对 n 取模的效果是一样的, 但是『&』与运算的性能要优于 hash 对 n 取模。

    3、hash冲突?怎么解决?

    当我们put<key,value>时,首先通过hash(key)计算得到的hash值,再通过『&』与运算之后,得到了数组存储位置bucket,但此时出现了两个不同的key却计算出相等的bucket,举个例子:

    数组A[0]位置计算出存放<张三,我是张三>数据,而在put<李四,我是李四>数据时,也计算为存放在A[0]位置,一个位置想存放两个数据?这就出现hash冲突了,怎么处理呢?

    JDK是这样处理的,它会在这个位置(A[0])挂一个链表,这个链表表里面存放出现冲突的数据,即:让多个<key,value>数据同时放在数组的一个位置里。

    get(key)时怎么取呢?当我们调用get(key)定位到数组位置时,如果发现这个位置挂载的是一个链表,那么就遍历链表,从里面找到自己想要的那个<key,value>数据。

    格外补充:这个地方,在JDK1.8之后引入了红黑树的概念,首先我们看一下为啥要引入红黑树,如果没有引入红黑树,当数组挂载的链表达到一定长度之后,查询是非常耗时的,性能比较差,时间复杂度为:O(n)「读作:偶en」。

    JDK1.8的优化就是,当链表的长度发到了一定长度后(8)会自动转换为红黑树,遍历一棵红黑树查找一个元素的时间复杂度为:O(logn)「读作:偶,老个en」,性能相对链表要高一些。

    简单总结一下:

    1. 出现hash冲突的原因?两个不同的key计算出相同的数组存放位置;

    2. 初期是怎么解决的?在出现数组冲突的位置挂一个链表,实现存放多个数据。

    3. JDK1.8的优化?当数组长度达到一定值后自动转换为红黑树,降低时间复杂度。

    4、HashMap是如何扩容的?

    HashMap底层是一个数组,当数组满了之后,他会自动进行2倍扩容,用于盛放更多的数据。

    比如,本来数组默认长度=16,扩容后*2=32。

    扩容后还有一步操作:rehash,重新对每个hash值进行寻址,也就是用每个hash值跟新的数组长度 n-1 进行『&』与运算操作。

    补充:扩容之后的与运算可能会导致之前的发生hash冲突的元素不再发生冲突。

    延伸一:之前一直在背的面试题中, 提到 HashMap 在多线程是不安全的「死循环」,为啥呢?或为什么说HashMap可能会导致死循环?
     
    这个过程就是发生在扩容阶段,在jdk1.7之前,hashmap在扩容到2倍新容器时,由于采用的是头插法「头插法就是总是把新增结点插在头部」,会造成链表翻转形成闭环,也就是形成死循环,jdk1.8之后就不再采用头插法了,而是直接插入链表尾部,因此不会形成环形链表形成死循环,但是在多线程的情况下仍然是不安全的,在put数据时如果出现两个线程同时操作,可能会发生数据覆盖,引发线程不安全,总之,用ConcurrentHashMap没错了。 
     
    延伸二:这为什么说HashTable效率低下呢?
     

    HashTable使用synchronized关键字来保证线程安全。当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法就会进入阻塞或轮训状态。这个的同步方法包括读和写,可以理解HashTable只有一把锁,所有的线程不管做什么,都是竞争这一把锁,例如线程1使用put进行元素添加,线程2不但不能使用put来添加元素,也不能使用get方法来获取元素,显然这效率是多低。

    博客地址:https://www.cnblogs.com/niceyoo

  • 相关阅读:
    171. Excel Sheet Column Number (Easy)
    349. Intersection of Two Arrays (Easy)
    453. Minimum Moves to Equal Array Elements (Easy)
    657. Judge Route Circle (Easy)
    CSS笔记
    保存页面状态
    UI开发总结
    ubuntu 下配置munin
    反向代理配置
    JavaScript 高级程序设计第二版
  • 原文地址:https://www.cnblogs.com/niceyoo/p/12508024.html
Copyright © 2011-2022 走看看