从网上已经身边同事朋友的面试状况来看,面试
HashMap
几乎是必问的,网上也不少相似的文章,可是真面起来,发现仍是有不少点能够深抠的。本篇就结合一次面试经历说一下以前没有注意的点吧。java
这个相信不用我多说,你们都知道HashMap
的底层是Node
数组结构Node<K,V>[] table
。面试
扩容也不用我多说了,在size
达到阈值(默认0.75
的负载因子*容量)时触发扩容。数组
数组的capacity
大小是2的x幂也无需多言,但这里多问一句为何是2的x幂而不是其余数呢?咱们知道,当一个key
被放进到数组时须要明确本身被放在哪一个位置。最简单的固然就是对key
进行hash
以后h%n
肯定。而若是数组的长度n
是2的x幂,h%n
这个操做与h&(n-1)
是等价的,会更快。同时在扩容时,每一个key
须要从新肯定本身在数组中的index
,这时若是数组每一个位置的元素都变了一次,显然开销会比较大。可是若是n
是2的x幂,那么在扩容变成2n
后须要从新确认index
时,对某个table[index]
这个元素的新位置只有两种可能:1. 在原地不动(若是h&n
的高位为0),2. index+n
(h&n
的高位为1)。这样每一个元素移动的几率只有50%,显然会节约不少拷贝操做。框架
这个也是高频问点,你们也基本都清楚,JDK1.8以后,若是某位置的链表长度大于某个阈值以后,就会转为红黑树,防止链表深度过大,从而查询时复杂度达到o(n)
最坏状况。可是细问起来,若是没有认真看过putVal
方法中每一行代码,真的有的地方可能会忽略。好比:ide
MIN_TREEIFY_CAPACITY
默认64这个值,会触发一次扩容而并不会执行转树操做,因此链表的长度是能够超过8的。final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } }
key
应该放在左子树仍是右子树?这个由什么肯定?由于HashMap
的key
并不要求是Comparable
,而TreeMap
很显然key
是要知足Comparable
的,那么此时新来一个TreeNode
,左右肯定以什么为依据呢?TreeNode
应该挂在它父节点的左边仍是右边,挂在哪边均可以啊,只要我插入时按某个标准,查找时也按一样的标准,二者保持一致就能够了,对吧?跟到源代码,对于没有实现Comparable
的key
,比较一下hashCode就能够了。源码中的比较一句就是两个key
的hashCode
,使用的是System.identityHashCode(object)
这个native方法。static int tieBreakOrder(Object a, Object b) { int d; if (a == null || b == null || (d = a.getClass().getName(). compareTo(b.getClass().getName())) == 0) d = (System.identityHashCode(a) <= System.identityHashCode(b) ? -1 : 1); return d; }
还有你们都耳熟能详的东西我就不赘述了,面后也思考了一下,基础仍是很重要,仍是有不少指的深刻思考的地方,必定要打牢基础,可能准备了不少框架原理实践什么的,若是基础的没答好,这些应用层的东西准备的再好,可能也没机会跟面试官聊了,固然在面试中如何去引导面试官这一点也很重要,俗话说的好,把对方拉倒跟我一个低智商区,而后用我丰富的经验战胜他,这一点很重要,之后要多注意。code