1. HashMap 不是线程安全的,那如果多线程下,它是如何处理的?并且什么 情况下会发生线程不安全的情况?
HashMap
不是线程安全的,如果多个线程同时对同一个 HashMap
更改数据的话,会导致数据不一致或者数据污染。
如果出现线程不安全的操作时,HashMap
会尽可能的抛出 ConcurrentModificationException
防止数据异常。
当我们在对一个 HashMap
进行遍历时,在遍历期间,我们是不能对 HashMap
进行添加,删除等更改数据的操作的,否则也会抛出 ConcurrentModificationException
异常,此为 fail-fast
(快速失败)机制。
从源码上分析,我们在 put,remove
等更改 HashMap
数据时,都会导致 modCount
的改变,当 expectedModCount != modCount
时,则抛出 ConcurrentModificationException
。如果想要线程安全,可以考虑使用ConcurrentHashMap
。
而且,在多线程下操作 HashMap
,由于存在扩容机制,当 HashMap
调用resize()
进行自动扩容时,可能会导致死循环的发生。
2. 我们在使用 HashMap 时,选取什么对象作为 key 键比较好
我们在使用 HashMap
时,最好选择不可变对象作为 key
。例如 String
,Integer
等不可变类型作为 key
是非常明智的。
如果 key
对象是可变的,那么 key
的哈希值就可能改变。在 HashMap
中可变对象作为 Key
会造成数据丢失。因为我们再进行 hash & (length - 1)
取模运算计算位置查找对应元素时,位置可能已经发生改变,导致数据丢失。
3. 其他
HashMap
是基于 Map
接口实现的一种键-值对<key,value>
的存储结构,允许 null
值,同时非有序,非同步(即线程不安全)。HashMap
的底层实现是数组 + 链表 + 红黑树(JDK1.8
增加了红黑树部分)。
HashMap
定位元素位置是通过键 key
经过扰动函数扰动后得到 hash
值,然后再通过 hash & (length - 1)
代替取模的方式进行元素定位的。
HashMap
是使用链地址法解决 hash
冲突的,当有冲突元素放进来时,会将此元素插入至此位置链表的最后一位,形成单链表。当存在位置的链表长度 大于等于 8
时,HashMap
会将链表 转变为 红黑树,以此提高查找效率。
HashMap
的容量是 2
的 n
次方,有利于提高计算元素存放位置时的效率,也降低了 hash
冲突的几率(当数组长度为 2
的 n
次幂的时候,不同的 key
算出的 index
相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。)。
因此,我们使用 HashMap
存储大量数据的时候,最好先预先指定容器的大小为 2
的 n
次方,即使我们不指定为 2
的 n
次方,HashMap
也会把容器的大小设置成最接近设置数的 2
的 n
次方,如,设置 HashMap
的大小为 7
,则 HashMap
会将容器大小设置成最接近 7
的一个 2
的 n
次方数,此值为 8
。