HashMap原理浅析(1.8之前)

作为初学者,简单对HashMap的原理做一下总结(以1.8之前的HashMap为主)

数组

数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;

链表

链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。

哈希表

那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表。哈希表((Hash table)既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。

HashMap结合了数组和链表的速度优势

HashMap示意图
(index)0 (table)entry      
1 entry entry entry ...
2 entry      
3 entry      
... ...      
  • key,value是以entry的形式保存,entry中还保存了其他的数据,如next,hash等
  • HashMap的内部首先是一个entry[]数组(即示意图中的table列)
  • 多个entry可能存在同一角标中,这时这些entry就以链表的形式链接(即后加入的entry放在entry[]数组(table列)里,之前的entry放在后加入的entry的next中)
  • HashMap中定义了一个hash()方法,防止小容量HashMap中的key产生过多碰撞(即存入同一角标中)
  • HashMap初始大小是16,加载因子是0.75,当HashMap的容量达到初始大小*加载因子时会扩容,扩容到原来的二倍
  • HashMap有构造函数可以指定初始容量和加载因子
  • 如果可以,尽量减少HashMap的扩容次数,因扩容很耗时.
  • JDK1.8中,如果满足条件(entry[]的长度大于64,单个链表长度大于8),会将链表转为红黑树,增加读写效率.
  • JDK1.8中,使用了Node(extends Entry)这个新的内部类来替代Entry,内部只有少量代码优化(如使用了新的句法语句),实现基本相同.也许是为了与红黑树更匹配所以使用Node这个名字吧..

Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。

用例子来说明HashMap的内部结构:

  1. 一个key/value对是以entry的形式存入HashMap的entry[]数组中
  2. entry中保存了key,value,hash,next
  3. 这个entry在数组中的角标(index)由key运算得来(int index = key.hashCode()&(entry[].length-1))
  4. 如果多个key/value对都存在同一个index下,那么将entry(新)放在entry[]数组中,并让entry(新)的next等于entry(原)

其他:关于遍历entry的for循环很有趣,之前我以为for循环只能for(int i=0;i<10;i++)这种,但源码里使用了for(Entry<K,V> e = table[i]; e != null; e = e.next)的方式,真是学到了(实现了链表形式;用出了循环的新高度),只能说自己基础太差....

附:

Java8 HashMap源码解析

HashMap源码分析(基于JDK1.6)

HashMap 的底层原理(相比前两个浅显些)

图解数组和链表(补基础:数组和链表各自的原理/优缺点,解释了操作的时间复杂度概念(O(1)/O(n)等),很好的一篇文章)

猜你喜欢

转载自blog.csdn.net/weixin_42072135/article/details/81085314