现在是晚上11点了,学校屠猪馆的自习室因为太晚要关闭了,勤奋且疲惫的小鲁班也从屠猪馆出来了,正准备回宿舍洗洗睡,由于自习室位置比较偏僻所以是接收不到手机网络信号的,因此小鲁班从兜里掏出手机的时候,信息可真是炸了呀,小鲁班心想,微信群平时都没什么人聊天,今晚肯定是发生了什么大事,仔细一看,才发现原来是小鲁班的室友达摩(光头)拿到了阿里巴巴JAVA开发实习生的offer,此时小鲁班真替他室友感到高兴的同时,心里也难免会产生一丝丝的失落感,那是因为自己投了很多份简历,别说拿不拿得到offer,就连给面试邀的公司也都寥寥无几,小鲁班这会可真是受到了一万点真实暴击,不过小鲁班还是很乐观的,很快调整了心态,带上耳机,慢慢的走回了宿舍,正打算准备向他那神室友达摩取取经。

  片刻后~

  小鲁班:666,听说你拿到了阿里的offer,能透露一下面试内容和技巧吗

  达摩:嘿嘿嘿,没问题鸭,叫声爸爸我就告诉你

  小鲁班:耙耙(表面笑嘻嘻,心里MMP)

  达摩:其实我也不是很记得了(请继续装),但我还是记得那么一些,如果你是面的JAVA,首先当然是

  •   JAVA的基础知识:数据结构(Map,List,Set等),设计模式,算法,线程相关,IO/NIO,序列化等等
  •   其次是高级特征:反射机制,并发与锁,JVM(GC策略,类加载机制,内存模型)等等

  小鲁班:问这么多内容,那岂不是一个人都面试很久吗?

  达摩:不是的,面试官一般都会用连环炮的方式提问的。

  小鲁班:你说的连环炮是什么意思鸭?

  达摩:那我举个例子

  就比如问你HashMap是不是有序的?

  你回答不是有序的。那面试官就会可能继续问你,有没有有序的Map实现类呢?

  你如果这个时候说不知道的话,那这块问题就到此结束了。如果你说有TreeMap和LinkedHashMap。

  那么面试官接下来就可能会问你,TreeMap和LinkedHashMap是如何保证它的顺序的?

  如果你回答不上来,那么到此为止。如果你说TreeMap是通过实现SortMap接口,能够把它保存的键值对根据key排序,基于红黑树从而保证TreeMap中所有键值对处于有序状 态。LinkedHashMap则是通过插入排序(就是你put的时候的顺序是什么,取出来的时候就是什么样子)和访问排序(改变排序把访问过的放到底部)让键值有序。

  那么面试官还会继续问你,你觉得它们两个哪个的有序实现比较好?

  如果你依然可以回答的话,那么面试官会继续问你,你觉得还有没有比它更好或者更高效的实现方式。。无穷无尽深入,直到你回答不出来或者面试官认为问题到底了

  小鲁班捏了一把汗,我去。。。这是魔鬼吧,那我们来试试呗(因为小鲁班刚刚在自习室才看了这章的知识,想趁机装一波逼,毕竟刚刚叫了声爸爸~~)

  于是达摩and小鲁班就开始了对决:

  1.为什么用HashMap?

  • HashMap是一个散列桶(数组和链表),它存储的内容是键值对(key-value)映射
  • HashMap采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改
  • HashMap是非synchronized,所以HashMap很快
  • HashMap可以接受null键和值,而Hashtable则不能(原因就是equlas()方法需要对象,因为HashMap是后出的API经过处理才可以)

  2.HashMap的工作原理是什么?

  • HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,计算并返回的hashCode是用于找到Map数组的bucket位置来储存Node 对象。这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Node 。

  

  • 以下是HashMap初始化 ,简单模拟数据结构

    Node[] table=new Node[16]  散列桶初始化,table

   class Node {

    hash;//hash值

key;//键

    value;//值

    node next;//用于指向链表的下一层(产生冲突,用拉链法)

   }

  • 以下是具体的put过程(JDK1.8版)

1.对Key求Hash值,然后再计算下标
          2.如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的Hash值相同,需要放到同一个bucket中)
          3.如果碰撞了,则调用equals() 比较value,相同则替换旧值,不同则以链表的方式链接到后面
          4.如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表
          5.如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排)

  

  • 以下是具体get过程(考虑特殊情况如果两个键的hashcode相同,你如何获取值对象?)

  当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

  

  3.有什么方法可以减少碰撞?

  • 扰动函数可以减少碰撞,原理是如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这就意味着存链表结构减小,这样取值的话就不会频繁调用equal方法,这样就能提高HashMap的性能。(扰动即Hash方法内部的算法实现,目的是让不同对象返回不同hashcode。)
  • 使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。为什么String, Interger这样的wrapper类适合作为键?因为String是final的,而且已经重写了equals()和hashCode()方法了。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。

  4.HashMap中hash函数怎么是是实现的?

  我们可以看到在hashmap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。前面说过hashmap的数据结构是数组和链表的结合,所以我们当然希望这个hashmap里面的元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表。 所以我们首先想到的就是把hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,能不能找一种更快速,消耗更小的方式,我们来看看JDK1.8的源码是怎么做的(被楼主修饰了一下)

  

static final int hash(Object key) {
if (key == null){
return 0;
}
int h;
h=key.hashCode();返回散列值也就是hashcode
// ^ :按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
//其中n是数组的长度,即Map的数组部分初始化长度
return (n-1)&(h ^ (h >>> 16));
}

 

  简单来说就是

1.高16bt不变,低16bit和高16bit做了一个异或(得到的HASHCODE转化为32位的二进制,前16位和后16位低16bit和高16bit做了一个异或)
          2.(n-1)&hash=->得到下标

  5.拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

  之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

  6.说说你对红黑树的见解?

  

  

  1. 每个节点非红即黑
  2. 根节点总是黑色的
  3. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
  4. 每个叶子节点都是黑色的空节点(NIL节点)
  5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

  7.解决hash 碰撞还有那些办法?

  开放定址法。

  当冲突发生时,使用某种探查技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定的地址。

  按照形成探查序列的方法不同,可将开放定址法区分为线性探查法、二次探查法、双重散列法等。

  下面给一个线性探查法的例子  

  问题:已知一组关键字为(26,36,41,38,44,15,68,12,06,51),用除余法构造散列函数,用线性探查法解决冲突构造这组关键字的散列表。

  解答:为了减少冲突,通常令装填因子α由除余法因子是13的散列函数计算出的上述关键字序列的散列地址为(0,10,2,12,5,2,3,12,6,12)。

     前5个关键字插入时,其相应的地址均为开放地址,故将它们直接插入T[0],T[10),T[2],T[12]和T[5]中。
  当插入第6个关键字15时,其散列地址2(即h(15)=15%13=2)已被关键字41(15和41互为同义词)占用。故探查h1=(2+1)%13=3,此地址开放,所以将15放入T[3]中。
    当插入第7个关键字68时,其散列地址3已被非同义词15先占用,故将其插入到T[4]中。
    当插入第8个关键字12时,散列地址12已被同义词38占用,故探查hl=(12+1)%13=0,而T[0]亦被26占用,再探查h2=(12+2)%13=1,此地址开放,可将12插入其中。
    类似地,第9个关键字06直接插入T[6]中;而最后一个关键字51插人时,因探查的地址12,0,1,…,6均非空,故51插入T[7]中。

  8.如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

  默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。这个值只可能在两个地方,一个是原下标的位置,另一种是在下标为<原下标+原容量>的位置  

    9.重新调整HashMap大小存在什么问题吗?

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

  HashMap的容量是有限的。当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会逐渐提高。这时候,HashMap需要扩展它的长度,也就是    进行Resize。1.扩容:创建一个新的Entry空数组,长度是原数组的2倍。2.ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。

(这个过程比较烧脑,暂不作流程图演示,有兴趣去看看我的另一篇博文"HashMap扩容全过程")

  达摩:哎呦,小老弟不错嘛~~意料之外呀

  小鲁班:嘿嘿,优秀吧,中场休息一波,我先喝口水

  达摩:不仅仅是这些哦,面试官还会问你相关的集合类对比,比如:

  10.HashTable

  • 数组 + 链表方式存储
  • 默认容量: 11(质数 为宜)
  • put:
    • 索引计算 : (key.hashCode() & 0x7FFFFFFF)% table.length
    • 若在链表中找到了,则替换旧值,若未找到则继续
    • 当总元素个数超过容量*加载因子时,扩容为原来 2 倍并重新散列。
    • 将新元素加到链表头部
  • 对修改 Hashtable 内部共享数据的方法添加了 synchronized,保证线程安全。

  11.HashMap ,HashTable 区别

  • 默认容量不同。扩容不同
  • 线程安全性,HashTable 安全  
  • 效率不同 HashTable 要慢因为加锁

  12.我们可以使用CocurrentHashMap来代替Hashtable吗?

  • 我们知道Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。

  13.CocurrentHashMap(1.7)

  • CocurrentHashMap是由Segment数组HashEntry数组和链表组成
  • Segment是基于重入锁(ReentrantLock:一个数据段竞争锁,每个HashEntry一个链表结构的元素,利用Hash算法得到索引确定归属的数据段,也就是对应到在修改时需要竞争获取的锁。ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。
  • 核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性
  • 首先是通过 key 定位到 Segment,之后在对应的 Segment 中进行具体的 put操作如下。
    1. 将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。
    2. 遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。
    3. 不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
    4. 最后会解除在 1 中所获取当前 Segment 的锁。
  • 虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理。

    首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争,则利用 scanAndLockForPut() 自旋获取锁。

    1. 尝试自旋获取锁。
    2. 如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁获取,保证能获取成功。最后解除当前 Segment 的锁

·· 14.CocurrentHashMap(1.8)

  • 其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。
  • 其中的 val next 都用了 volatile 修饰,保证了可见性
  • 最大特点是引入了 CAS(借助 Unsafe 来实现【native code】)
    1. CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
    2. Unsafe 借助 CPU 指令 cmpxchg 来实现
    3. 使用实例:
      1. 对 sizeCtl 的控制都是用 CAS 来实现的
      1. sizeCtl :默认为0,用来控制 table 的初始化和扩容操作。

        • -1 代表table正在初始化
        • N 表示有 -N-1 个线程正在进行扩容操作
        • 如果table未初始化,表示table需要初始化的大小。
        • 如果table初始化完成,表示table的容量,默认是table大小的0.75倍,居然用这个公式算0.75(n - (n >>> 2))。
    4. CAS 会出现的问题:ABA
      • 解决:对变量增加一个版本号,每次修改,版本号加 1,比较的时候比较版本号。

  put过程

  • 根据 key 计算出 hashcode 。
  • 判断是否需要进行初始化。
  • 通过 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
  • 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
  • 如果都不满足,则利用 synchronized 锁写入数据。
  • 如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。

  get过程

  • 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
  • 如果是红黑树那就按照树的方式获取值。
  • 就不满足那就按照链表的方式遍历获取值。

  此时躺着床上的张飞哄了一声:睡觉了睡觉了~

  见此不太妙:小鲁班立马回到床上(泉水),把被子盖过头,心里有一丝丝愉悦感,不对。好像还没洗澡。。。

  by the way

  CocurrentHashMap在JAVA8中存在一个bug,会进入死循环,原因是递归创建ConcurrentHashMap 对象,但是在1.9已经修复了,场景重现如下

  

public class ConcurrentHashMapDemo{

    private Map<Integer,Integer> cache =new ConcurrentHashMap<>(15);

    public static void main(String[]args){
ConcurrentHashMapDemo ch = new ConcurrentHashMapDemo();
System.out.println(ch.fibonaacci(80));
} public int fibonaacci(Integer i){
if(i==0||i ==1) {
return i;
} return cache.computeIfAbsent(i,(key) -> {
System.out.println("fibonaacci : "+key);
return fibonaacci(key -1)+fibonaacci(key - 2);
}); }
}

  转载请注明出处,谢谢

  

  

HashMap?面试?我是谁?我在哪的更多相关文章

  1. 从HashMap面试聊聊互联网内卷

    微信公众号:大黄奔跑 关注我,可了解更多有趣的面试相关问题. 写在之前 毫无疑问,回想2020年有什么词出现在眼前最多的,无疑是"996"和"内卷",从马老师的 ...

  2. 记一次HashMap面试

    记一次HashMap面试 从网上已经身边同事朋友的面试情况来看,面试HashMap几乎是必问的,网上也很多类似的文章,但是真面起来,发现还是有很多点可以深抠的.本篇就结合一次面试经历说一下之前没有注意 ...

  3. HashMap面试知识点

    HashMap的工作原理是近年来常见的Java面试题. 几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如 ...

  4. 你不得不知道的HashMap面试连环炮

    为什么用HashMap? 简述一下Map类继承关系? 解决哈希冲突的方法? 为什么HashMap线程不安全? resize机制? HashMap的工作原理是什么? 有什么方法可以减少碰撞? HashM ...

  5. HashMap面试知识点总结

    主要参考 JavaGuide 和 敖丙 的文章, 其中也有参考其他的文章, 但忘记保存链接了, 文中图片也是引用别的大佬的, 请见谅. 新手上路, 若有问题, 欢迎指正. 背景 HashMap 的相关 ...

  6. 有关 HashMap 面试会问的一切

    前言 HashMap 是无论在工作还是面试中都非常常见常考的数据结构. 比如 Leetcode 第一题 Two Sum 的某种变种的最优解就是需要用到 HashMap 的,高频考题 LRU Cache ...

  7. 一万三千字的HashMap面试必问知识点详解

    目录 概论 Hasmap 的继承关系 hashmap 的原理 解决Hash冲突的方法 开放定址法 再哈希法 链地址法 建立公共溢出区 hashmap 最终的形态 Hashmap 的返回值 HashMa ...

  8. HashMap面试必问的数据结构相关知识

    如果在看这篇文章时,对HashMap的结构还不是很了解,建议你参考前段时间写的<刨死你系列——HashMap剖析(基于jdk1.8)>,可能会对下面的提及到知识点有些帮助. 1:HashM ...

  9. HashMap面试必问的6个点,你知道几个?

    一.HashMap的实现原理? 此题可以组成如下连环炮来问 你看过HashMap源码嘛,知道原理嘛? 为什么用数组+链表? hash冲突你还知道哪些解决办法? 我用LinkedList代替数组结构可以 ...

  10. HashMap面试总结

    作者:孤独烟 出处: http://rjzheng.cnblogs.com/ 文章由点及线再及面,写的非常好.修改部分内容 (1) HashMap的实现原理 看过HashMap源码吗,知道原理吗? h ...

随机推荐

  1. 使用清华源替代Ubuntu源

    sudo nano /etc/apt/source.list 替换为如下文本 deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial main ...

  2. api大全

    免费api大全(更新中) API大全  http://www.apidq.com/    (这个碉堡了) 天气接口 气象局接口 完整数据:http://m.weather.com.cn/data/10 ...

  3. jquery和ajax的关系详细介绍【转】

    jquery和ajax的关系详细介绍 http://www.jb51.net/article/43965.htm

  4. DOM4J熟知

    什么是解析xml 系统最终会从xml中读取数据. 读取的过程就是解析. CRUD ==> 增删改查 ==> create read update delete ==> 解析指的就是读 ...

  5. java项目获取根路径(web项目和application项目的区分)

    Java项目中经常要读取配置文件,涉及到读取配置文件的地方,就会要读定位文件的路径.因此,在项目如何正确获取文件路径尤为关键. 根据不同的java项目,在获取文件路径时候有一些 小区别 测试环境:E: ...

  6. FineReport启动后访问404

    近期将FineReport以嵌入式方式部署在Tomcat8上,启动服务后,点击导出下载出现HTTP ERROR 404情况: 百思不得其解啊,纠结了好几天: 后查看原部署Tomcat6服务器的cata ...

  7. 关于easyui Datagrid一些样式记录

    此篇文章主要记录在使用datagrid中常见的修改样式方式以及样式效果配图!!!! 一丶存在选中框的时候标题栏合并显示序号字段. 代码展示: onLoadSuccess: function (data ...

  8. DropZone(文件上传插件)

    1. html文件 dropzone的原理是模拟表单来上传文件,html中的元素有多重形式. 可以建立一个form表单: <form id="dropz" action=&q ...

  9. git实用命令

    http://blog.sina.com.cn/s/blog_4ce89f2001014qvr.html 1.git clone                                     ...

  10. tomcat设置端口号,访问指定ip就访问指定项目

    1.修改背景: A.通常我们访问我们的web应用格式为: http://ip:端口号/项目名称 例如: http://127.0.0.1:8080/projectName B.如果想直接输入" ...