HashMap?面试?我是谁?我在哪

欢迎工做一到八年的Java工程师朋友们加入Java高级交流:854630135node

本群提供免费的学习指导 架构资料 以及免费的解答面试

不懂得问题均可以在本群提出来 以后还会有直播平台和讲师直接交流噢算法

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

片刻后~数组

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

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

小鲁班:baba(表面笑嘻嘻,内心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?面试?我是谁?我在哪

如下是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.若是碰撞了,以链表的方式连接到后面

4.若是链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表

5.若是节点已经存在就替换旧值

6.若是桶满了(容量16*加载因子0.75),就须要 resize(扩容2倍后重排)

如下是具体get过程(考虑特殊状况若是两个键的hashcode相同,你如何获取值对象?)
当咱们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置以后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

HashMap?面试?我是谁?我在哪

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));
}

HashMap?面试?我是谁?我在哪

简单来讲就是

1.高16bt不变,低16bit和高16bit作了一个异或(获得的HASHCODE转化为32位的二进制,前16位和后16位低16bit和高16bit作了一个异或)

2.(n·1)&hash=->获得下标

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

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

6.说说你对红黑树的看法?

HashMap?面试?我是谁?我在哪

 

每一个节点非红即黑
根节点老是黑色的
若是节点是红色的,则它的子节点必须是黑色的(反之不必定)
每一个叶子节点都是黑色的空节点(NIL节点)
从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)
 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.ConcurrentHashMap 原理

最大特色是引入了 CAS(借助 Unsafe 来实现【native code】)
CAS有3个操做数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改成B,不然什么都不作。
Unsafe 借助 CPU 指令 cmpxchg 来实现
使用实例:
对 sizeCtl 的控制都是用 CAS 来实现的
 
sizeCtl :默认为0,用来控制 table 的初始化和扩容操做。
-1 表明table正在初始化
N 表示有 -N-1 个线程正在进行扩容操做
若是table未初始化,表示table须要初始化的大小。
若是table初始化完成,表示table的容量,默认是table大小的0.75倍,竟然用这个公式算0.75(n - (n >>> 2))。
CAS 会出现的问题:ABA
对变量增长一个版本号,每次修改,版本号加 1,比较的时候比较版本号。
13.咱们可使用CocurrentHashMap来代替Hashtable吗?

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

相关文章
相关标签/搜索