1.HashMap的工作原理,其中get()方法的工作原理?
HashMap基于hash原理,通过put()
和get()
方法存储和获取元素。它内部使用数组+链表或红黑树的结构,通过hash运算找到bucket位置来存储Entey对象,通过equals()
方法找到正确的键值对。HashMap使用链地址法来解决hash碰撞问题,当发生碰撞时,对象会存储在链表的下一个节点处。
get()
首先通过计算key的hash值找到在数组中的下标,若下标处元素为空则直接返回null,否则判断该下标处元素是否是要查找的元素,是就直接返回,不是就判断该元素节点是否为红黑树节点,是就进行红黑树节点的查找,否则遍历链表进行查找。
关键点:HashMap是在bucket中存储键对象和值对象作为Map.Entry
。
2.我们能否让HashMap同步?
通过Map m = Collections.synchronizeMap(hashMap)
实现同步。在synchronizeMap
中给HashMap的所有操作增加synchronized
竞争监视器锁来实现线程同步。
关键点:synchronizeMap
或ConcurrentHashMap
。
3.关于HashMap中的哈希冲突(哈希碰撞)以及冲突解决办法?
HashMap通过计算key的hash值来确定数组位置,不同的key可能会产生相同hash值,因此会发生hash碰撞,在HashMap中采用链地址法来解决hash冲突,当发生碰撞是,将相同hash值的元素存储在链表的下一个节点处。除此之外还有开放地址法,再散列法。
关键点:产生碰撞原因,一个好的hash函数可以降低碰撞。
4.如果HashMap的大小超过负载因子定义的容量会怎么办?
HashMap中默认的负载因子为0.75,当HashMap中数组元素个数超过负载因子乘以数组总容量时会发生扩容,将数组扩展为原来的两倍,对原数组中的元素再进行hash运算,获得在新数组中的位置。
关键点:扩容!!!。
5.你了解重新调整HashMap大小存在什么问题吗?
多线程条件下对于HashMap进行操作确实会存在同步问题,看网上说调整HashMap大小会导致在头部添加元素从而避免尾部循环,然后就死循环了…其实我没懂!!!
6.为什么String, Interger这样的wrapper类适合作为键?
HashMap推荐使用不可变变量作为键,其中String最为常用,因为String是不可变的,也是final的,我们通过计算key的hash值在数组中进行查找,如果key是可变类型,那么在插入和获取时返回的hashcode不同,就无法正确获得我们想要的对象。
7.我们可以使用自定义的对象作为键吗?
可以,但是要确保这个对象重写了hashCode()
和equals()
方法,并且该对象插入Map后就不再改变,只要遵循了这些,它就已经可以作为键了。
8.我们可以使用CocurrentHashMap来代替Hashtable吗?
可以,并且推荐使用CocurrentHashMap
。HashTable采用synchronized
实现同步,对其中所有方法添加监视器锁,问题在于,在一个线程进行put操作时,其他线程无法获得get或其他操作,而CocurrentHashMap
采用分段锁技术,比HashTable提供更强的线程安全。
9.HashMap扩容问题?
扩容是是新建了一个HashMap的底层数组,而后调用transfer
方法,将就HashMap的全部元素添加到新的HashMap中(要重新计算元素在新的数组中的索引位置)。 很明显,扩容是一个相当耗时的操作,因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。因此,我们在用HashMap的时,最好能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能。
10.为什么HashMap是线程不安全的?如何体现出不安全的?
当多个线程对HashMap就行put操作时,如果put的key相同会产生碰撞,那么HashMap会将两个key放在数组的同一位置,其中一个线程的key会被覆盖。
当多个线程检测到数组需要扩容时,都会进行数组中元素hash值的重新计算和数据复制,那么也势必造成最后只有一个线程创建的数组成功赋值给table。
11.能否让HashMap实现线程安全,如何做?
- 使用线程安全的HashTable,当一个线程访问HashTable的同步方法时,其他线程会被阻塞。也就是说当一个线程进行get操作,其他线程不能进行任何操作,效率很低。
- 使用
Collections.synchronizeMap(hashMap)
。但是它的效率也不高,不信去看源码,看完之后会发现它跟HashTable具有同样的问题。 - 使用
CocurrentHashMap
。
12.HashMap中hash函数是怎么实现的?
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
hash通过计算key自身的hashCode()
值,,返回的类型是int,为了减少碰撞产生的几率,将获得的hashCode()
的低16位与高16位右移16位相异或。
13.HashMap什么时候需要重写hashcode和equals方法?
当key值为对象时必须要求重写hashcode()
和equals()
方法。因为默认的hashcode()
使用的是该值在内存中的地址作为该值的hash值,如果两个具有相同意义的对象进行比较时,由于其地址不同会导致计算出的hash值也不同。而重写equals()
可以确保两个对象具有相同意义的属性。