什么时候会用到HashMap?他有什么特点
是基于Map接口实现的,存储键值对时,可以接收null的键值,是非同步的,HashMap存储着Entry(hash,key,value,next)对象。
你知道HashMap的工作原理吗
通过hash方法,通过put和get存储和获取对象。存储对象是,我们把键值对传递给put方法时,它调用key的hashCode方法计算hash从而得到bucket位置,进一步存储,如果发生碰撞,HashMap会通过链表将产生碰撞的元素组织起来。HashMap会根据当前bucket的占用情况自动调整容量(超过loadFactor会resize为原来的2倍)。获取对象时,将Key穿给get方法,他调用key的hashCode方法计算hash获得bucket位置,并进一步调用equals方法确定键值对。在JDK8中,如果一个bucket中冲撞的元素数量超过8时,会使用红黑树替换链表,从而提升速度
你知道get和put的原理吗?equals()和hashCode()都有什么作用?
通过key的hashCode方法进行hash,并通过(n-1&hash)的方法计算下标,从而获取buckets(桶下标)的位置,如果发生碰撞则采用key.equals()方法去链表或红黑树中查找对应的节点
你知道hash的实现吗?为什么要这样实现
在java8的实现中,是通过hashCode()的高16位异或低16位也就是扰动函数实现的。(h = key.hashCOde()) ^ (h>>>16),主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中,同时不会有太大的开销
如果HashMap的大小超过了负载因子(loadFactor)定义的容量怎么办?
如果超过了负载因子,则会resize一个长度两倍的HashMap,并且在Java7中进行链表元素迁移的时候会进行rehash。而在java8中进行了一些优化,不需要进行rehash,什么优化呢?由于HashMap的长度为2的次幂所以扩容的时候需要迁移的链表元素有两种情况,要么桶下标不变,要么变为桶下标大小加上原来的HashMap的长度大小,决定一个链表元素是那种情况只在于新加入的1bit是0还是1。
当两个对象的hashCode相同会发生什么
因为hashCode相同(不代表对象地址相同),所以他们的bucket位置相同,‘碰撞’会发生。在put操作执行时发生碰撞,这个Entry会储存在链表中,如果两个对象的hashCode一样,将会通过equals()比较值决定是否采取覆盖行为(返回true),还是插入到链表中(返回false)
如果两个键的hashCode相同,你如何获取值对象
找到bucket位置后,会调用keys.equals()方法去链表找到正确的节点,最终找到要找的值对象。因此,设计HashMap的key类型时,如果使用不可变的、声明为final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变能够缓存不同键的hashCode,这将提高整个获取对象的速度。、
如果HashMap的大小超过了负载因子(loadFactor)定义的容量怎么办?
默认的负载因子大小为0.75,也就是说,当一个map填满了3/4的bucket的时候,就会进行resize操作,重新创建一个大小为原来两倍的bucket数组,并将原来的对象放入新的bucket数组中,在java7时链表元素转移时会进行rehash而在java8中做了一些优化性能的操作不需要进行rehash
你了解重新调整HashMap大小存在什么问题吗?
当resize的时候确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来(java7会出现这种情况,java8做了优化不会使用头插法),因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部(同样是java7出现的情况),这是为了避免尾部遍历。如果条件竞争发生了,那么就死循环了。因此在并发环境下,我们使用CurrentHashMap来替代HashMap
为什么String,Integer这样的wrapper类适合作为建?
因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入和获取的时候返回不同的hashCode的话,那么就不能从HashMap中找到想要的对象。不可变性还有其他的优点如线程安全。见对象重写这两个方法是非常重要的。