HashMap几乎是面试必问的知识,对于HashMap面试是你真的能从容面对吗?相信若是你去面试知名互联网公司的时候,决对不会只是问问你HashMap的数据结构这么简单的问题。我收集了最近老大在面试过程当中关于HashMap常问的几个问题:java
new HashMap(14);
HashMap是由数组+链表(1.8还有红黑树)来实现的,那么上面这行代码它执行后,建立的数组大小是多少呢?
追踪源码能够看到它会执行这样一个函数来返回数组大小的:node
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
图解:面试
经过这个函数的运算,能够将咱们传入的14运算获得16,也就是大于14的最小的2的n次幂。编程
上面说明了数组大小最后会保证是2的n次幂,那么接下来讲说为何要保证是2的n次幂数组
static int indexFor(int h, int length) { return h & (length-1); }
在jdk1.7的时候,在put元素时,会执行这样一段代码片断,它的用意就是数据长度与hashCode值取余运算。那既然是取余,为何不直接用%号呢?是由于位运算要比%运算高效不少。安全
那既然是&运算,又为何非要保证length是2^n呢?数据结构
加载因子是很是重要的一块,若是加载因子太大,假如为1,那么从空间利用率却是上去了,可是时间效率就下降了。
若是加载因子过小,倒致使hashmap频繁的扩容操做,每次扩容都很是耗性能;
好吧!说了就像没说同样,关于这个问题我也只能抛砖引玉;
实际上是这样的:多线程
Because TreeNodes are about twice the size of regular nodes, we * use them only when bins contain enough nodes to warrant use * (see TREEIFY_THRESHOLD). And when they become too small (due to * removal or resizing) they are converted back to plain bins. In * usages with well-distributed user hashCodes, tree bins are * rarely used. Ideally, under random hashCodes, the frequency of * nodes in bins follows a Poisson distribution * (http://en.wikipedia.org/wiki/Poisson_distribution) with a * parameter of about 0.5 on average for the default resizing * threshold of 0.75, although with a large variance because of * resizing granularity. Ignoring variance, the expected * occurrences of list size k are (exp(-0.5) * pow(0.5, k) / * factorial(k)). The first values are: * * 0: 0.60653066 * 1: 0.30326533 * 2: 0.07581633 * 3: 0.01263606 * 4: 0.00157952 * 5: 0.00015795 * 6: 0.00001316 * 7: 0.00000094 * 8: 0.00000006 * more: less than 1 in ten million
选择0.75是空间和时间的一个折中,也并非说,非必须是0.75,其它的编程语言也有配置成0.72的。less
void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } }
提及这个话题,当时在网上找博客看是真没有能看懂的,因此我尽可能用图的方式来表述dom
Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; }
看下方图文分析:
因此,jdk1.8中的HashMap在扩容时就不会产生死锁了!
首先,TreeNode节点的占用空间的大小是链表节点的两倍,只有当容器达到8的时候才转为红黑树,为何是8呢,在第二个问题中已经说明了,根据泊松分布能够看出,链表节点是很难达到长度为8的时候的,若是真有特殊状况达到8了,那么才将链表转为红黑树;
转为红黑树时还有个要求,就是hashMap中的元素个数达到64。
JDK1.8HashMap虽然可以尽大的避免扩容时死循环问题,可是,HashMap仍然是线程不安全的,例如:线程A在put元素时,线程B进行扩容;之因此不安全的缘由是多线程会操做同一实例变化,致使变量状态不一致;