1. 存储结构
为提高查找效率,HashMap采用了数组+链表+红黑树的方式存储数据。主干是个数组,该数组的每个元素都是一个Node<K,V>。
2. 工作原理
新增一个元素时,会调用HashMap的put()方法。根据key计算hash值,根据hash值和数组长度得到数组下标。
不同的key有可能hash值相同,即该位置的数组中的元素出现两个,这就是哈希碰撞,为提高查找效率,hashmap采用链表形式进行存储。
相同的key,hash值也可能相同,则会遍历对应下标下的链表,新的Node覆盖旧的Node。
在jdk8中,当链表长度超过阈值(8)时,就会将链表转换为红黑树,这样大大减少了查找时间[查询时间从O(n)变为了O(logn)]。
3. 扩容
hashMap的主干是数组,当使用空间达到总空间的0.75时,就要对数组进行扩容。新建一个长度为之前数组2倍的新的数组,把原数组中的元素放入新数组中,同时把原数组中的引用置null,以便垃圾回收。
当重新调整HashMap大小的时候,多线程下存在条件竞争(race condition),因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,对应链表中元素的次序会反过来,因为移动到新数组的位置时,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。
4. 重写equals方法需同时重写hashCode方法
put和get都首先会调用hashcode方法,去查找相关的key,当有冲突时,再调用equals方法。
若不重写hashcode方法,则存入该元素时的hash值和取出该元素的hash值不同,就会出现返回null的情况。
put和get操作相同,key-->hash-->index->最终索引位置 ,由于二者的hash值不同,导致没有定位到数组的同一个下标位置而返回逻辑上错误的值null。若碰巧定位到一个数组位置,也会判断其hash值是否相等,不相等,还是会返回null。
5. 多线程下使用HashMap
线程安全上:HashTable > ConcurrentHashMap > HashMap(线程不安全)
HashTable是线程安全的,因为它的所有CRUD操作都被synchronized修饰,但在线程竞争激烈的情况下HashTable的效率非常低下。ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁,效率比HashTable高。故ConcurrentHashMap实现了更高级的线程安全,推荐。
6. 参考