HashMap简介

HashMap是Java中最常用的集合类框架,是Java语言中非常典型的数据结构,也是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。
键值对:Entry(Key , Value)
HashMap在获取元素时,可以通过Key来获取Value。HashMap有如下特点:

  • 根据键的HashCode值存储数据
  • 只允许一条记录的键为null,允许多条记录的值为null(允许null值)
  • 线程不安全
  • 底层是HashTable(哈希表)
  • 遍历无序
  • 初始长度为16
  • 默认加载因子0.75

数组和链表的特点

数组:

  • 连续存储
  • 查找效率高
  • 添加、删除操作效率较低

链表:

  • 离散存储
  • 添加、删除操作速度快,扩展灵活
  • 不能随机查找

HashMap底层原理

  • HashMap底层实际上是数组+链表的结合,它结合了数组和链表的优点,使得查找、添加、删除操作的效率都较高。
  • 我们知道HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些键值对分散存储在一个数组当中,这个数组就是HashMap的主干数组元素的每一个Entry都是一个链表的头结点,链表主要为了解决哈希冲突而存在。

map.put(K,V)实现原理

  1. 例如,我们现在需要插入一个Key为“Student”,Value为“杰哥”的元素,此时的键值对Entry为(Student,杰哥)。调用方法map.put(“Student”,“杰哥”)将Entry添加到HashMap。
  2. 调用方法后,HashMap底层调用Key的HashCode方法得出Hash值,并将Hash值转换为数组的下标(index)。
  3. 假设下标值为“2”,如果2这个位置上没有任何元素,则把键值对添加到“2”这个位置。此时数组中的结构如下图:
  4. 如果我们又添加了另一个Entry,而计算出它的下标值也正好是“2”,此时数组中“2”的位置上已经有了之前添加的一个Entry,这样就会造成冲突。解决这种情况的办法就是使用链表。
  5. 第一个Entry为链表头结点,发生冲突时将第二个Entry插入至链表即可,插入时使用的是头插法,因为HashMap的发明者认为,后插入的Entry被查找的可能性更大,插入后的HashMap如下图:
  6. 在插入链表时会使用Key和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新添加进来的结点将正常添加。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。

map.get(K)实现原理

  1. 假设我们要通过Key“Student”找到Value“杰哥”,调用方法map.get(“Student”)。
  2. HashMap底层调用Key的HashCode方法得出Hash值,并将Hash值转换为数组的下标(index),假设为“2”。
  3. 通过下标快速定位某个位置,并与该位置Entry的Key值进行equals,如果为false,则证明此位置无内容。
  4. 如果这个位置有链表,则会与该链表每个结点的Key进行equals,如果返回true,则表示找到对应Key值的结点。
  5. 最终get方法返回该位置的value。

其它

在JDK1.8之后会有些许变化:

  • 链表插入时从头插法变更为尾插法
  • 链表长度超过8时会转换为红黑树
  • 当红黑树上的节点数量小于6个时,会重新把红黑树变成单向链表数据结构