HashMap特性

HashMap存储的是键值对,允许为null,key值不可重复,如果重复,value将被覆盖。

非同步,线程不安全,无序

底层原理

jdk8后采用的是:数组+链表+红黑树

当使用put方法时,会先对key做一个hashcode值计算,找到在bucket数组中的位置,来存储Entry对象;如果hashcode值相同就通过equals方法进行比较;如果equals方法返回false,就将数据以链表的形式存储在数组对应的位置,并将之前在该位置的数据往链表的后面移动(头插法),并记录一个next属性,来指示后移的那个数据。

当使用get方法时,同上

头插法

HashMap会将Entry对象不断插入链表的头部,这样也防止来尾遍历,否则Entry每次添加还要定位到尾节点。如果条件竞争(多线程操作),可能会出现环形链表,当使用get操作时,有可能发生死循环。

put方法如何实现

  1. 计算key的hashcode值
  2. 如果散列表为空时,调用resize()方法初始化散列表
  3. 如果没有发生碰撞,直接将元素添加到散列表中
  4. 如果发生了hash碰撞,进行判断,如果key值相同,替换value值;如果是链表结构,在链表的尾部进行插入,如果链表个数达到红黑树的阀值8,转红黑树结构;如果是红黑树结构,调用树的插入方法。
  5. 如果元素总数大于阀值,就会调用resize()方法进行扩容

扩容实现resize()方法

默认负载因子大小为0.75,当一个map填满75%的bucket时,将会创建原HashMap2倍大小的bucket,并将原来的对象重新计算放入新的bucket中。

哈希碰撞

key值不同,但是通过hashCode计算的值却一样。发生碰撞后,HashMap使用LinkedList存储对象(键值对)

为什么引入红黑树

jdk1.8以前,数据结构是数组+链表

当hashMap中有大量的数据都存放在一个桶中时,这个桶就会有一个很长的链表,时间遍历复杂度是O(n),针对这种问题,引入了红黑树来优化。

为什么String,Integer这样的Wrapper类适合作为主键

String是不可变类,String/Integer都重写了hashCode()和equals()方法。如果使用对象的话,就需要重写其hashCode()和equals()方法。并且这个对象是不可变的。