最近在详细的研究hashmap的内部结构和原理,终于豁然开朗,原来hashmap是那么的完美,数组和链表的结合体。

在学习hashmap之前,首先问大家几个问题,看看是否对hashmap有了解多少,咱们通过问题进行对hashmap的学习和探索。

1、HashMap 是不是有序的?不是有序的。

2、有没有有序的Map实现类呢?有 TreeMap 和 LinkedHashMap。

3、然后问TreeMap 和 LinkedHashMap 是如何保证它的顺序的? TreeMap 是通过实现 SortMap 接口,能够把它保存的键值对根据 key 排序,基于红黑树,从而保证 TreeMap 中所有键值对处于有序状态。LinkedHashMap 则是通过插入排序(就是你 put 的时候的顺序是什么,取出来的时候就是什么样子)和访问排序(改变排序把访问过的放到底部)让键值有序。

4、你觉得它们两个(Treemap和LinkedHashMap)哪个的有序实现比较好?如果你依然可以回答的话,那么继续问你,你觉得还有没有比它更好或者更高效的实现方式?

为什么要使用HashMap?

  • HashMap是一个散列桶(数组和链表),它存储的内容是键值对key-value映射
  • HashMap采用了数组和链表的数据结构,能在查询和修改方面继承了数组的线性查询和链表的寻址修改
  • HashMap是非synchronized的,线程不安全,所以很快
  • HashMap可以接受null键和值,而HashTable不行(因为equals()方法需要对象,因为HashMap是后出的api经过处理才可以)

HashMap的工作原理是什么?

了解HashMap工作原理之前我觉得有必要了解下hashcode和equals的小知识,见  

然后讲下HashMap中的实现原理,hashmap是由数组和链表组成的。数组是HashMap的本体,而链表则是为了解决hash冲突而存在的,如果定位到数组位置不存在链表(当前Entry的next指向为null),那么对于查找插入等操作很快,仅仅需要一次寻址即可;如果定位到数组有链表,对于添加操作其时间复杂度为O(n),首先遍历链表,存在既覆盖,否则新增。对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

添加元素操作步骤:

以下是具体的 put 过程(JDK1.8

  1. 对 Key 求 Hash 值,然后再计算下标
  2. 如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的 Hash 值相同,需要放到同一个 bucket 中)如果碰撞了,以链表的方式链接到后面
  3. 如果链表长度超过阀值(TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表
  4. 如果节点已经存在就替换旧值
  5. 如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排)

HashMap LinkedHashMap TreeMap 哪个是有序的 hashmap是否有序_链表

减少hash冲突?扰动函数可以减少碰撞

  1. 原理是如果两个不相等的对象返回不同的 hashcode 的话,那么碰撞的几率就会小些。这就意味着存链表结构减小,这样取值的话就不会频繁调用 equal 方法,从而提高 HashMap 的性能(扰动即 Hash 方法内部的算法实现,目的是让不同对象返回不同hashcode)。
  2. 使用不可变的、声明作 final 对象,并且采用合适的 equals() 和 hashCode() 方法,将会减少碰撞的发生不可变性使得能够缓存不同键的 hashcode,这将提高整个获取对象的速度,使用 String、Integer 这样的 wrapper 类作为键是非常好的选择。

为什么 String、Integer 这样的 wrapper 类适合作为键?

因为 String 是 final,而且已经重写了 equals() 和 hashCode() 方法了。不可变性是必要的,因为为了要计算 hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的 hashcode 的话,那么就不能从 HashMap 中找到你想要的对象。