一)HashMap基本简述
HashMap底层主要是基于数组和链表实现的,数组中存放的元素就是一个单向链表,因此我们可以将HashMap理解为单向链表数组。
在HashMap源码中我们可以看到其中一些关键属性,例如:transient Entry[] table; 这个就是用来存储元素的实体数组,其中数组的类型为Entry,这与我们上面对HashMap定义是一致的。
1 transient Entry[] table;
二)HashMap的get操作
HashMap进行数据存储时会通过key的hashCode计算出hash值,然后根据hash值去计算出应存储的数组索引(存储在哪个数组中,对应索引是多少),实际就是存储在数组中的单向链表中,且位于链的头部。而在完成数据put之后,需要判断size(存放元素的个数)是否达到了临界值(threshold),若达到临界值就进行扩容,扩容后的大小为当前大小的两倍。扩容时调用的是resize(),由于在扩容时原数组中的数据必须重新计算其在新数组中的位置并放进去,因此是非常消耗性能的操作,如果我们能预知HashMap中元素的个数,那么预设元素个数能够有效提升HashMap的性能。
补充说明:但其实HashMap这个扩容设计有个隐含的缺陷,因为是先put后扩容若后面再无put操作则该次扩容就是无效扩容,不仅浪费了容量也消耗了性能。
三)HashMap的put操作
HashMap进行get数据操作时,会先通过key的hashCode计算出hash值,根据hash值计算得到存储的数组的索引,再通过key的equals方法在链表中找到需要的元素,在链表中查找元素通过循环来实现。
四)HashMap的局限性
由于HashMap是非线程安全的,因此多线程put时若同时引发resize(),则链表就很容易形成闭合的链路,导致再get时出现死循环、CPU占用率为100%。因此进行多线程编程时应该使用ConcurrentHashMap。
五)其它说明
数组的容量为什么一定要是2的整数次幂:是为了减小不同的hash值发生碰撞的概率,使元素在数组中均匀地散列,相对地查询时不用遍历某个位置上的链表提高了效率。