参考博客:https://blog.youkuaiyun.com/Yoga0301/article/details/84452104
HashMap原理
①基于数组+链表+红黑树。
②当调用put方法时,首先计算元素key的hash值,以此确定插入数组中的位置,但是可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,但是形成了链表,同一各链表上的Hash值是相同的,。而当链表长度太长时,链表就转换为红黑树,这样大大提高了查找的效率。
③当链表数组的容量超过初始容量的0.75时,将数组扩大2倍,把原数据的搬移到新的数组中。
1.数组Node<k,v>
hash; key; value; Node<k,v> next;
2.为什么需要使用加载因子,为什么需要扩容呢?
“哈希冲突”和“空间利用率”矛盾的一个折衷。
加载因子大,哈希冲突几率大,导致插入数据慢。
加载因子小,空间利用率低。
3.hashmap的主要参数都有哪些?
数组初始化容量 DEFAULT_INITIAL_CAPACITY=1<<4;默认值为16
数组极限容量 MAXIMUM_CAPACITY = 1 << 30;
加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f;
扩容临界值 int threshold;
4.当两个对象的hashcode相同会发生什么?
hash碰撞。调用key.eqauls(),相同则覆盖value,不相同则添加为链表,链表很长就会变成红黑树。
5.hash算法 http://www.cnblogs.com/ysocean/p/9054804.html
hash算法:计算key值对应哈希桶的位置,让元素分散均匀,从而提高查询效率。
#数组在获取元素会比链表快,所以我们应该尽量让每个哈希桶只有一个元素,这样在查询时就只需要通过索引值找到对应的哈希桶内的值,而不需要再通过桶内的链表一个一个去查。
代码实现:
①获取key.hashcode
②高位参与运算 右移16位,高位补0。并与前面第一步获得的hash码进行按位异或^ 运算。这是为了当length比较小的时候,也能保证考虑到高低Bit位都参与到Hash的计算中,同时不会有太大的开销。
③取余运算,但在计算机运算中&比%快,又因为h % n = h &(n - 1) n是table的长度
6.put图解
7.扩容
最耗性能的操作。
扩容的步骤是先对size扩大两倍,再对原先的节点重新经过hash算法得到新的索引值即复制到新的哈希桶里。最后得到新的table。
其中jdk8对扩容进行了优化,提高了扩容的效率。但在平常运用中尽量要避免让hashmap进行扩容,若已知hashmap中的元素数量,则一开始初始化hashmap时指定容量,这样就减少了hashmap扩容次数。
四、常见的面试题
2.HashMap 和 Hashtable 的区别
线程安全。HashMap是线程不安全的,而HashTable是线程安全的,每个方法通过修饰synchronized来控制线程安全。
效率 HashMap比HashTable效率高。原因在于HashTable的方法通过synchronized修饰后,并发的效率会降低。
允不允许null 。HashMap运行只有一个key为null,可以有多个null的value。而HashTable不允许key,value为null。
3.HashMap的长度为什么是2的倍数
在HashMap的操作流程中,首先会对key进行hash算法得到一个索引值,这个索引值就是对应哈希桶数组的索引。为了得到这个索引值必须对hashcode跟数组长度进行取余运算。即 hash % n (n为hashmap的长度),又因为&比%运算快。n如果为2的倍数,就可以将%转换为&,结果就是 hash & (n-1)。所以这就解释了为什么HashMap长度是2的倍数。
4.Jdk1.8中满足什么条件后将链表转化成红黑树?
很显然在putVal方法中是判断桶内的节点个数是否大于8,之后通过treeifyBin方法中判断长度是否大于最小红黑树容量64,小于则继续扩容,大于则转为红黑树。
//putVal方法判断桶内元素是是否大于8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
//treeifyBin方法中判断长度是否大于最小红黑树容量64,小于则继续扩容,大于则转为红黑树
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();