HashMap源码分析

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是初始化

 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     }
putMapEntries

   int size()、isEmpty():这两个比较简单,要了解size()的复杂度是O(1)就可以了

   Node<K,V> getNode(int hash, Object key):主要是为了实现V Map.get(K key)、containsKey(Object key)等相关方法

 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     }
getNode

   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等

 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 }
resize

  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),这些方法看方法名就能够看出用处。

  后面很多涉及到函数式编程的方法,有兴趣自行百度,之后我会再更新函数式编程相关博客

转载于:https://www.cnblogs.com/farseer/p/6790005.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值