1.类说明
是一个以hash表为基础的Map接口的实现。提供了所有的map操作,并且允许key值和value值为空。HashMap和Hashtable操作一模一样,只是HashTable是同步的并且不允许null值这点区别。HashMap不保证元素的顺序性,也不保证元素的顺序是持久不变的,大家知道HashSet是无序集合,其实HashSet就是基于HashMap实现的。
2.继承和实现
继承了AbstractMap,实现了Map、Cloneable、Serializable。AbstractMap也实现了map,主要对map接口的一些操作提供了骨架实现,最小化接口实现多需要的操作。如果要实现一个不可修改的map,我们只需要继承这个类并且提供一个entryset的实现方法就可以了,entryset是map键值对的set集合展示。这个set不应该支持add和remove操作,它的迭代器不应该支持remove操作。如果想要实现一个可变map,需要我们实现put方法(必要时抛出UnsupportedOperationException),并且通过entrySet().Iterator()方法必须提供remove方法。实现类必须要提供一个无参构造和一个参数为map的的构造器。Map接口定义了一系列操作,
3.成员变量
int DEFAULT_INITIAL_CAPACITY:默认存储大小,必须要是2的次方,默认16个
int MAXIMUM_CAPACITY:最大的存储,1<<30
float DEFAULT_LOAD_FACTOR:默认加载因子,当实际存储量达到定义存储量的百分之多少时,扩容map,默认0.75f
TREEIFY_THRESHOLD:jdk1.8新增成员变量,达到某个阈值,将对应entry的存储方式改成红黑树存储,不用默认链表的方式存储,默认是8
UNTREEIFY_THRESHOLD:jdk1.8新增成员变量,达到某个阈值,将对应entry的存储方式改成链表的方式存储,不用红黑树存储,默认是6
MIN_TREEIFY_CAPACITY:jdk1.8新增成员变量,当hashmap的存储容量达到这个值的时候,开始将链表存储改变成红黑树存储,这个和TREEIFY_THRESHOLD的区别是这个值指的是table数组的长度,不是链表的长度,默认是64
Node<K,V>[] table:jdk1.8修改成员变量(1.7中是Entry<K,V> table,二者没区别,因为Node继承Entry),存储数据的结点,map的存储结构如图1所示
Set<Map.Entry<K,V>> entrySet:查询结果使用的,将存储的数据存放在set集合中
int size:map中key-value对存储的个数
int modCount:map结构化修改次数
int threshold:下一次重新计算存储大小的值,默认DEFAULT_INITIAL_CAPACITY*DEFAULT_LOAD_FACTOR
float loadFactor:加载因子,默认为DEFAULT_LOAD_FACTOR
图1 map存储结构
4.构造方法
四个构造方法,如下所示
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
// 因为要求初始化大小为2的倍数,所以这里处理一下
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
// 该方法还在putAll中用到,成员方法细说
putMapEntries(m, false);
}
5.成员方法&静态方法
static final int hash(Object key):返回对应key.hashCode()的hash值,主要用于散列存储和查找。①-->(h = key.hashCode()) ^ (h >>> 16)<--②
①key的hashCode值,不用多说,返回值为int
②h >>> 16,丢弃低16位的值,当h < (1 << 16)即65535的时候,运算就等于是0,0与任何一个数亦或运算都是这个数本身。所以仅当h大于65536的时候,会重新调整返回值
主要是为了让key充分散列存储,减少hash冲突,至于为什么使用亦或,可以查看这个知乎的解答,链接:https://www.zhihu.com/question/20733617
static Class<?> comparableClassFor(Object x):判断x是否继承了Comparable接口,因为红黑树是有序树,树内结点是需要通过某种比较排列得出的
static int compareComparables(Class<?> kc, Object k, Object x):判断x是否继承了KC,如果是二者进行比较,用在树化结构中
static final int tableSizeFor(int cap):通过给定的table容量重新计算容量大小,计算结果为最接近cap的2次幂值,具体代码解释可以查看这篇博客http://www.tuicool.com/articles/yqyIVzI
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict):putAll和构造方法使用,将m的值放入table中,evict为false表示map是初始化
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { 2 int s = m.size(); 3 if (s > 0) { 4 // 当前map,这里没有进行不为空且s<threshold的判断,因为每一步putVal操作都会进行是否需要扩容的判断 5 if (table == null) { 6 // 当前存储容量大小 7 float ft = ((float)s / loadFactor) + 1.0F; 8 int t = ((ft < (float)MAXIMUM_CAPACITY) ? 9 (int)ft : MAXIMUM_CAPACITY); 10 if (t > threshold) { 11 // 扩容 12 threshold = tableSizeFor(t); 13 } 14 } else if (s > threshold){ 15 // 如果存放的值大于阈值,将map存储的数据重新排列 16 resize(); 17 } 18 // 循环放入 19 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { 20 K key = e.getKey(); 21 V value = e.getValue(); 22 // 放入map中,下面详述此方法 23 putVal(hash(key), key, value, false, evict); 24 } 25 } 26 }
int size()、isEmpty():这两个比较简单,要了解size()的复杂度是O(1)就可以了
Node<K,V> getNode(int hash, Object key):主要是为了实现V Map.get(K key)、containsKey(Object key)等相关方法
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 final Node<K,V> getNode(int hash, Object key) { 2 Node<K,V>[] tab; Node<K,V> first, e; int n; K k; 3 // 如果当前node数组不为null&&长度部位0 4 // tab[(n-1)&hash],其中hash是通过hash()方法获得的,hash()方法主要是对key进行hashCode运算,然后&(n-1)运算相当于取模运算,主要是为了获取当前key存在与Node[]的位置,总要从第一个元素开始获取 5 if ((tab = table) != null && (n = tab.length) > 0 && 6 (first = tab[(n - 1) & hash]) != null) { 7 // always check first node,从这里我们看出了从hashmap中取数据的逻辑,先对key的hashCode进行hash运算,找到对应Node链表,然后判断二者hash值、key值是否相同,相同的话返回该结点,Map的get方法返回该结点的值 8 if (first.hash == hash && 9 ((k = first.key) == key || (key != null && key.equals(k)))) 10 return first; 11 // 判断是否有下一个元素 12 if ((e = first.next) != null) { 13 // 判断是否是树状结构 14 if (first instanceof TreeNode) 15 return ((TreeNode<K,V>)first).getTreeNode(hash, key); 16 // 循环判断 17 do { 18 if (e.hash == hash && 19 ((k = e.key) == key || (key != null && key.equals(k)))) 20 return e; 21 } while ((e = e.next) != null); 22 } 23 } 24 return null; 25 }
V putValue(int hash, K key, V value, boolean onlyIfAbsent, boolean evict):实现HashMap的put操作及相关方法
1 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { 2 Node<K,V>[] tab; Node<K,V> p; int n, i; 3 // 如果table为null,即当前HashMap为空,初始化table size,resize()方法后面会提到 4 if ((tab = table) == null || (n = tab.length) == 0){ 5 n = (tab = resize()).length;// n为默认的16 6 } 7 // p为table[i]中Node链表的首元素 8 // 如果p为空,(n - 1) & hash这个操作是计算key在table数组中的索引 9 // 即存在哪个table中,算法等同于hash%(n-1)但是效率极高 10 if ((p = tab[i = (n - 1) & hash]) == null) { 11 // 直接将newNode放入table[i]中 12 tab[i] = newNode(hash, key, value, null); 13 } 14 else { 15 Node<K,V> e; K k; 16 // 如果key的hash值相同并且key相同,就可以认为是对key的更新 17 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))){ 18 e = p; 19 } 20 // 如果p是树结点 21 else if (p instanceof TreeNode){ 22 // 将p放入红黑树中,这里涉及到红黑树的插入,还是很复杂的,想了解的可以去搜寻相关知识 23 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 24 } 25 else { 26 // 非树结点,这个for的操作,表示从Node链表首元素开始,去迭代Node链表 27 // 第一个if表示Node在链表中不存在,进行赋值操作,第二个if表示存在,后期返回老的value值即可 28 for (int binCount = 0; ; ++binCount) { 29 // 看图1可以看到Node链表的结构,如果p.next()为空,将其指向新插入的Node,将新Node的next置为空 30 if ((e = p.next) == null) { 31 p.next = newNode(hash, key, value, null); 32 // 迭代过程中,如果迭代次数超过指定的树化阈值时,需要将链表结构转换为红黑树结构 33 if (binCount >= TREEIFY_THRESHOLD - 1){ // -1 for 1st 34 treeifyBin(tab, hash); 35 } 36 break; 37 } 38 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { 39 break; 40 } 41 p = e; 42 } 43 } 44 // 这里表示key存在的情况 45 if (e != null) { // existing mapping for key 46 V oldValue = e.value; 47 // 如果onlyIfAbsent为true,表示不更改存在的值,即不对老数据进行更改 48 if (!onlyIfAbsent || oldValue == null){ 49 e.value = value; 50 } 51 // 这个方法似乎是为LinkedHashMap所准备的,源码中就只有一句话,move node to last 52 afterNodeAccess(e); 53 // 修改返回老的value值 54 return oldValue; 55 } 56 } 57 ++modCount; 58 // 这里更改一下map中size的大小,如果超过阈值,就要将hashMap扩容 59 if (++size > threshold){ 60 resize(); 61 } 62 // 这个方法似乎是为LinkedHashMap所准备的,源码中就只有一句话,possibly remove eldest 63 // 如果用户定义了removeEldestEntry的规则,那么便可以执行相应的移除操作 64 afterNodeInsertion(evict); 65 // 新增结点返回值为null 66 return null; 67 }
Node[K, V] resize():初始化或者扩容时候调用,一切会影响到map结构的操作都会调用,比如put、remove等
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 final Node<K,V>[] resize() { 2 Node<K,V>[] oldTab = table; 3 int oldCap = (oldTab == null) ? 0 : oldTab.length; 4 int oldThr = threshold; 5 int newCap, newThr = 0; 6 if (oldCap > 0) { 7 if (oldCap >= MAXIMUM_CAPACITY) { 8 threshold = Integer.MAX_VALUE; 9 return oldTab; 10 } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) 11 // double threshold 12 newThr = oldThr << 1; 13 // initial capacity was placed in threshold 14 } else if (oldThr > 0) { 15 newCap = oldThr; 16 // zero initial threshold signifies using defaults 17 } else { 18 newCap = DEFAULT_INITIAL_CAPACITY; 19 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 20 } 21 if (newThr == 0) { 22 float ft = (float)newCap * loadFactor; 23 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); 24 } 25 threshold = newThr; 26 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 27 table = newTab; 28 // 核心是if这段,这里只解读这段,oldTab表示需要被扩容的map 29 if (oldTab != null) { 30 for (int j = 0; j < oldCap; ++j) { 31 Node<K,V> e; 32 if ((e = oldTab[j]) != null) { 33 // 清空table[j],即Node数组中下标为j的Node链表 34 oldTab[j] = null; 35 // 当前Node链表只有 一个元素,直接将Node放入Node数组即可 36 if (e.next == null) { 37 // e.hash & (newCap - 1) 用来计算元素存放位置,该操作等同于e.hash % (newCap - 1) 38 newTab[e.hash & (newCap - 1)] = e; 39 // 红黑树处理,红黑树相关之后说 40 } else if (e instanceof TreeNode) { 41 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 42 } else { 43 Node<K,V> loHead = null, loTail = null; 44 Node<K,V> hiHead = null, hiTail = null; 45 Node<K,V> next; 46 do { 47 // e节点的下一个节点 48 next = e.next; 49 // e.hash & oldCap会有两个结果,一个是0,一个是oldCap 50 // 如果为0,对应节点的数据会放在原来位置的Node链表中,如果是oldCap,对应的节点数据会放到j+oldCap的位置的Node链表中 51 if ((e.hash & oldCap) == 0) { 52 // do的第一次执行,将头节点和尾节点( loTail = e)都设置成e 53 if (loTail == null) { 54 loHead = e; 55 } else { 56 // 之后执行,将上一个节点.next指向当前节点 57 loTail.next = e; 58 } 59 loTail = e; 60 } else { 61 // 这里处理扩容产生影响的Node,先存放在一个新的Node链表中,原理同上 62 if (hiTail == null) { 63 hiHead = e; 64 } else { 65 hiTail.next = e; 66 } 67 hiTail = e; 68 } 69 } while ((e = next) != null); 70 if (loTail != null) { 71 // 将Node链表最后一个元素的next节点置为NULL 72 loTail.next = null; 73 // 将新的Node链表存放到原来的位置 74 newTab[j] = loHead; 75 } 76 if (hiTail != null) { 77 hiTail.next = null; 78 // 将新的Node链表存放到扩容之后的位置 79 newTab[j + oldCap] = hiHead; 80 } 81 } 82 } 83 } 84 } 85 return newTab; 86 }
void treeifyBin(Node<K, V) tab, int hash):将所有需要树化的Node链表改成树状结构,链表过小的直接resize即可
void putAll(Map<? extends K, ? extends V> m):将m复制到当前map中,如果m为空,抛出NullPointerException,具体调用的是putVal方法
V remove(Object key):移除key对应的键值对,如果存在,返回key对应的value值,否则返回null
void clear()和boolean containsValue(Object value)相对较简单,不多说
Set<K> keySet()、Collection<V> values():map中key的Set集合、value的集合类
6.jdk1.8新方法
V getOrDefault(Object key, V defaultValue)、V putIfAbsent(K key, V value)、boolean remove(Object key, Object value)、boolean replace(K key, V oldValue, V newValue)、V replace(K key, V value),这些方法看方法名就能够看出用处。
后面很多涉及到函数式编程的方法,有兴趣自行百度,之后我会再更新函数式编程相关博客