继承了abstractMap, 实现了map、cloneable、serializable接口
初始参数
默认初始化容量16
最大容量1<<30(约10亿)
默认负载因子0.75
阈值为容量*负载因子
结构被修改次数modCount(修改val不算)
构造函数
hashMap(初始容量,负载因子)
hashMap(初始容量):加上默认负载因子
hashMap(): 默认初始容量,默认负载因子
hashMap(Map): 新建一个hashMap, 容量为(传入的map的容量/负载因子 + 1)和默认容量的最大值,并用map的元素填充新hashMap
方法
hash(h):静态,默认访问权限。
这个有点儿复杂,只是记下h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);
indexFor(h, length): 静态,默认访问权限。
h&(length-1) 这里可以看出容量选择2的幂的原因了:lenght-1之后二进制所有位都是1(除最高位以上),进而有快速屏蔽h高位的效果
get(key): 对于key为null,直接查找table数组的第一位(下标为0);非null的键,先hash出在table数组中的下标,再检索Entry链表
put(key, value): 对于key为null,放到table数组的第一个。非null的key,若存在,更新val,返回原始值;不存在则新建entry,将table原来该位置的entry链放到新entry的后边(即头插法,听说jdk8采用尾插法)。元素个数大于阈值,resize扩大一倍长度
resize(newCapacity): 默认访问权限。
如果容量已经是最大容量则该阈值为Integer最大值,不再扩容。否则,创建新数组,将原来的元素rehash放入数组。
这里会有一个问题(面试官会考的哦),就是链表会出现无线循环。问题出在这行代码 e.next = newTable[i];
上,e结点位于原数组上的一条链表,newTable[i]是对应的扩容后的新数组上的链表(对应的新链表)的头结点(随着扩容的进行,原链表的元素从头到尾,往新链表的头部进行插入)。
当两个线程同时进行resize的时候就存在这样一种情况:e.next = e(死循环),也就是newTable[i]=e了。为什么会出现这种情况呢? 想象一下只有一个线程的情况,代码执行到下一行才会将e赋值给newTable[i], 也就是newTable[i]指向了已e开头的新链表。也就不会存在e=e的情况。
为啥多线程就会有问题了呢?就是因为存在另一个线程会在当前线程准备执行e.next = newTable[i]
的时候,提前执行了newTable[i]=e
。所以就有了死循环。
remove(key):根据键删除有键值的Entry对象。
clear(): table数组的每一项都置为null。modCount加1。
containsValue(value):map是否包含value值对应的键值对。分为null和非null的查找。
clone(): 对当前hashMap浅拷贝,key和value不会被拷贝。
keySet(): 返回key的set集合。
values(): 返回value的集合。
entrySet(): 返回Entry的集合。
内部类
Entry: 保存key、value的对象,实现了hashCode()、equal()、toString()方法。
hashIterator: 迭代器。及其子类ValueIterator、KeyIterator、EntryIterator,只了重写next方法。
keySet: 继承abstractSet,为keyset()方法服务。
Values: 继承了abstractCollection, 为values()方法服务。
EntrySet: 继承了abstractSet, 为entrySet()方法服务。
相关类
一些继承或者相关的类
LinkedHashMap
继承了hashMap。在内部维护了一个双向链表,在新增或删除元素的时候,对双向链表进行维护。具体的存储查找删除元素还是hashmap的逻辑,在遍历map的时候用的是双向链表,不会遍历没有hash到的位置,所以更高效。再有就是新map提供遍历顺序是插入顺序还是访问顺序。