HashMap概述
在数据结构中,物理存储的数据结构只有两种,即数组和链表,其余的如树,队列,栈等数据结构都是在它们的基础上逻辑抽象而来Hashmap实际上是一个“链表散列”的数据结构,是数组和链表的结合体(允许使用null值和null键)
由图可知,HashMap底层就是一个数组结构,数组中的每一个元素又是一个链表,因此它综合了数组存储和链表存储的优点,通过这种组合实现了查找和增删效率的平衡。其中,最基本的单元是Entry。
HashMap的实现
当有新的Entry需要加入进来时,根据hash值得到Entry在数组中的位置下标(通过Entry的Key哈希后得到值和table数组的长度位与运算得到数组的下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾(遍历链表,通过equals方法逐个比较key,如果有相同的Key就覆盖,如果没有就追加到链表头部)。
图中红框的Entry构成数组,绿框中的Entry构成链表。
HashMap的扩容
当hashmap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对hashmap的数组进行扩容。
HashMap中数组的默认初始长度是16(源码决定),当数组中有数据的个数超过总数的0.75时,HashMap就会自动扩容(扩大一倍)。一旦扩容,就意味着,旧的HashMap中的Entry全部要迁移到新的HashMap中,也就是rehash(最消耗性能)。扩容的关键在于Entry的key通过什么样的哈希算法重新定位到table中的位置。
自动扩容机制,会导致性能消耗。着启示我们,如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。比如,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适。
总结
HashMap由数组加链表组合体,数组可视为HashMap的哈希桶,链表则是为解决哈希碰撞而存在的,如果定位到的数组位置不含链表(即哈希桶中只有一个Entry),那么对于查找,添加等操作很快,仅需一次寻址即可。;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,也需遍历链表,然后通过key对象的equals方法逐一比较查找。所以,性能考虑,HashMap中的链表出现越少,性能就会越好。这也暗示着key的哈希值越离散,Entry就会尽可能的均匀分布,出现链表的概率也就越低