1.HashMap是AbstractMap的子类,用于存储key-value对的数据,其根据key的hashcode来存储键值对。HashMap又有LinkedHashMap等子类。HashMap是线程不安全的,但效率较高。
2.HashMap的结构:
(1)HashMap的底层结构是数组+链表+红黑树。HashMap中存储的是包含了key和value的entry对象,其使用Set来存储entry。而key也是使用Set来存储的,由于key要求无序且不可重复,所以存放的key所属的类中必须要重写equals()方法和hashCode()方法,这一点与HashSet是一样的。而value是无序但可以重复,所以只需要重写value所属的类的equals()方法即可。
(2)HashMap的红黑树结构:在JDK8中,为了提高链表过长时的查找性能,加入了红黑树。当HashMap数组中某个位置上的链表所存储的数据超过8个,并且整个数组的长度超过了64时,就会将这条链表中的数据改成红黑树存储;当这条链表上存储的数据减小到8个以下时,又会从红黑树恢复成链表。
3. HashMap put()方法的过程:
(a)当我们要向一张hashmap中添加一个元素时,首先会根据该元素的key值,取得其hashcode,然后判断该hashmap中有无数据,如果没有,则调用resize()函数创建好数组;之后通过散列函数计算得出它应存放的数组位置。
(b)之后,会对该位置进行判定,如果没有值,则直接存入;如果有值,则要将存放在该位置的链表中的所有元素的key值逐一与新添加的元素的key值进行比较。比较过程如下:
(b-1)首先,对比地址值是否一致,若一致则说明该元素已经存在,直接退出比较,进行(c)
(b-2)若地址值不一致,则调用equals()方法(注意是被重写过的equals()方法)对比值是否相同,若一致则说明该元素已存在,退出比较进行(c);
(b-3)若皆不一致,则比较链表中的下一个元素,直至比较完成
(c)完成比较后,如果是元素已存在,则进行替换,把原本已存在的entry对象的value值替换为新添加的元素的value值,并将旧的被替换下来的value值返回;如果不存在,则将新添加的元素添加到链表末尾。添加之后,需要判断数组是否需要扩容,流程如下:
(c-1)首先,判断在添加了该元素后,链表长度是否超过了8,且数组长度是否超过了64;若是,则需将该链表转为红黑树形式存储;
(c-2)其次,判断添加了该元素后,数组已用数量是否达到了阈值(数组长度的75%);若是,且数组长度还未达到上限,则需对数组进行扩容,扩为两倍。
4. HashMap 其它常用方法:
(a)remove(key);
(b)get(key);
(c)clear(); 需要注意,clear()方法只是清除hashmap中存放的数据,但hashmap本身还在
(d)containsKey(key)和containsValue(value);返回值为true/false,用于判断hashmap中是否存放有某个数据,两者区别在于根据key值查找还是根据value值查找,根据value值查找的,只要查找到一个就不会继续查找
(e)size();
(f)isEmpty();
(g)equals(Object);判断当前的hashmap对象和目标Object是否相等。这里的比较,会对比所有键值对的键和值,例如键相同但值不同则返回结果仍是false。
(h)entrySet(), keySet(),分别用于遍历键值对、键。与其它集合一样,这种遍历也需要借助Iterator完成。
(i)value();
5.LinkedHashMap:LinkedHashMap是HashMap的子类,其在HashMap的基础上,在其底层结构中添加了一对指针,可以指向前一个数据和后一个数据。相较于HashMap,它可以保证在遍历时按照添加的顺序遍历(即具有链表的特点),适用于需要频繁遍历的情况。
(1)LinkedHashMap的put()方法是直接调用的HashMap中的put()方法,而重写了newNode()方法和Entry()方法,其重写的Entry()方法中添加了before和after两个元素,记录了该元素的前后元素。