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

  • 相关阅读:
    1. Visual Basic概述
    4. HTML5
    Android 操作SQLite基本用法
    android开发规范
    ListView与ArrayAdapter的搭配使用
    个人笔记——Android网络技术
    Java中forEach, 用来遍历数组
    详细讲解Android的网络通信(HttpUrlConnection和HttpClient)
    简单使用URLConnection、HttpURLConnection和HttpClient访问网络资源
    Http编程之HttpClient
  • 原文地址:https://www.cnblogs.com/niceyoo/p/12508024.html
Copyright © 2011-2022 走看看