HashMap源码分析-jdk1.7

注:转载请注明出处!!!!!!!这里咱们看的是JDK1.7版本的HashMap

学习HashMap前先知道熟悉运算符合

 *左移 << :就是该数对应二进制码整体左移,左边超出的部分舍弃,右边补零。举个例子:253的二进制码1111 1101,在经过运算253<<2后得到1111 0100。很简单  

*右移 >> :该数对应的二进制码整体右移,左边的用原有标志位补充,右边超出的部分舍弃。

*无符号右移 >>> :不管正负标志位为0还是1,将该数的二进制码整体右移,左边部分总是以0填充,右边部分舍弃。

*取模运算 h & (length-1) 就是 h%lenght ,但是&比%具有更高的效率

总结下 X>>Y  相当于X/(2^Y)  X<<Y 相当于X*(2^Y)

知道了运算后,先看创建HashMap的4个构造方法

 1 /**
 2      * Constructs an empty <tt>HashMap</tt> with the specified initial
 3      * capacity and load factor.
 4      *
 5      * @param  initialCapacity the initial capacity
 6      * @param  loadFactor      the load factor
 7      * @throws IllegalArgumentException if the initial capacity is negative
 8      *         or the load factor is nonpositive
 9      */
10 
11     //初始化HashMap,使用传入的初始容量和加载因子
12     public HashMap(int initialCapacity, float loadFactor) {
13         //初始容量不能小于0否则抛异常
14         if (initialCapacity < 0)
15             throw new IllegalArgumentException("Illegal initial capacity: " +
16                                                initialCapacity);
17         //如果自定义的初始容量大于默认的最大容量(1<<30=2^30)则将默认     
18         //最大容量赋值给传入的初始容量
19         if (initialCapacity > MAXIMUM_CAPACITY)
20             initialCapacity = MAXIMUM_CAPACITY;
21         //如果加载因子小于等于0或者加载因子不是一数字,抛异常
22         if (loadFactor <= 0 || Float.isNaN(loadFactor))
23             throw new IllegalArgumentException("Illegal load factor: " +
24                                                loadFactor);
25 
26         //当前加载因子=传入加载因子
27         this.loadFactor = loadFactor;
28         //扩容变量 = 传入初始容量
29         threshold = initialCapacity;
30         //初始化
31         init();
32     }
33 
34 /**
35      * Constructs an empty <tt>HashMap</tt> with the specified initial
36      * capacity and the default load factor (0.75).
37      *
38      * @param  initialCapacity the initial capacity.
39      * @throws IllegalArgumentException if the initial capacity is negative.
40      */
41 
42     //初始化HashMap传入一个自定义的初始容量,默认的加载因子(0.75)
43     public HashMap(int initialCapacity) {
44         //走上面的初始化方法
45         this(initialCapacity, DEFAULT_LOAD_FACTOR);
46     }
47 
48     /**
49      * Constructs an empty <tt>HashMap</tt> with the default initial capacity
50      * (16) and the default load factor (0.75).
51      */
52      //初始化HashMap,使用默认的初始容量(1<<4= 2^4 =16)
53      //和默认的加载因子(0.75)
54     public HashMap() {
55        //同上
56        this(DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR);
57     }
58 
59  
60 
61     /**
62      * Constructs a new <tt>HashMap</tt> with the same mappings as the
63      * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
64      * default load factor (0.75) and an initial capacity sufficient to
65      * hold the mappings in the specified <tt>Map</tt>.
66      *
67      * @param   m the map whose mappings are to be placed in this map
68      * @throws  NullPointerException if the specified map is null
69      */
70     
71     //构造一个映射关系与指定 Map 相同的新 HashMap
72     public HashMap(Map<? extends K, ? extends V> m) {
73         //调用自己的构造,取出最大初始容量,默认加载因子
74         this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
75                       DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
76         //创建一个Entry[],初始化Hash掩饰码
77         inflateTable(threshold);
78         //将m的值put到新的HashMap中或者创建一个新的HashMap
79         putAllForCreate(m);
80     }
View Code

 以上是HashMap的4种构造方法,下面我们来一步步分析源码

 1 /**
 2      * The default initial capacity - MUST be a power of two.
 3      */
 4     //HashMap的默认初始化容量  2^4 = 16
 5     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
 6 
 7     /**
 8      * The maximum capacity, used if a higher value is implicitly specified
 9      * by either of the constructors with arguments.
10      * MUST be a power of two <= 1<<30.
11      */
12     //HashMap的默认最大容量  2^30 = 1073741824
13     static final int MAXIMUM_CAPACITY = 1 << 30;
14 
15     /**
16      * The load factor used when none specified in constructor.
17      */
18     //HashMap的默认加载因子 
19     //(注:当前容量 >= 最大容量 * 0.75 时会进行扩容)
20     static final float DEFAULT_LOAD_FACTOR = 0.75f;
21 
22     /**
23      * An empty table instance to share when the table is not inflated.
24      */
25     //一个空的Entry[] 哈希表
26     static final Entry<?,?>[] EMPTY_TABLE = {};
27 
28     /**
29      * The table, resized as necessary. Length MUST Always be a power of two.
30      */
31     //(transient:表示不进行序列化)将上面的Entry赋值过来,始终未2的幂
32     transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
33 
34     /**
35      * The number of key-value mappings contained in this map.
36      */
37     // 表示HashMap的键值映射数目,已存元素个数
38     transient int size;
39 
40     /**
41      * The next size value at which to resize (capacity * load factor).
42      * @serial
43      */
44     // If table == EMPTY_TABLE then this is the initial capacity at which the
45     // table will be created when inflated.
46     //扩容变量  size>=threshold 时就会扩容
47     int threshold;
48 
49     /**
50      * The load factor for the hash table.
51      *
52      * @serial
53      */
54     //加载因子
55     final float loadFactor;
56 
57     /**
58      * The number of times this HashMap has been structurally modified
59      * Structural modifications are those that change the number of mappings in
60      * the HashMap or otherwise modify its internal structure (e.g.,
61      * rehash).  This field is used to make iterators on Collection-views of
62      * the HashMap fail-fast.  (See ConcurrentModificationException).
63      */
64     //修改次数
65     transient int modCount;
66 
67     /**
68      * The default threshold of map capacity above which alternative hashing is
69      * used for String keys. Alternative hashing reduces the incidence of
70      * collisions due to weak hash code calculation for String keys.
71      * <p/>
72      * This value may be overridden by defining the system property
73      * {@code jdk.map.althashing.threshold}. A property value of {@code 1}
74      * forces alternative hashing to be used at all times whereas
75      * {@code -1} value ensures that alternative hashing is never used.
76      */
77     //这个常量在静态类部类中Holder使用过,可能影响初始化hash掩饰码
78     static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
View Code

以上HashMap的参数解释,下面看看我们最常用的 put(K,V) 和 get(K)

先来看看put(K,V)

 1  /**
 2      * Associates the specified value with the specified key in this map.
 3      * If the map previously contained a mapping for the key, the old
 4      * value is replaced.
 5      *
 6      * @param key key with which the specified value is to be associated
 7      * @param value value to be associated with the specified key
 8      * @return the previous value associated with <tt>key</tt>, or
 9      *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
10      *         (A <tt>null</tt> return can also indicate that the map
11      *         previously associated <tt>null</tt> with <tt>key</tt>.)
12      */
13 
14     //这里put 会返回 value 一般情况来说为空,发送冲突后value会返回发生冲突前的值
15     public V put(K key, V value) {
16         //根据上面的参数解释可以得到结果 第一次为true
17         if (table == EMPTY_TABLE) {
18             //第一次put会新建一个Entry[]赋值给table,初始化table的大小 2的幂
19             inflateTable(threshold);
20         }
21         //map的key可以为空就是在这
22         if (key == null)
23             //key为null都会将value存放在table[0]中
24             //第一次加入时:修改次数+1,元素数量+1
25             //如果有冲突则返回老值
26             //这里也会进行容量判断如果当前容量大于等于默认容量则扩容
27             //扩容为table.length*2 具体扩容放在后面解释
28             return putForNullKey(value);
29         //生成当前key的哈希值
30         int hash = hash(key);
31         //生成一个下标
32         int i = indexFor(hash, table.length);
33         //这里看当前下标中table是否有值
34         for (Entry<K,V> e = table[i]; e != null; e = e.next) {
35             Object k;
36              //如果有值则判断 hash值和当前生成的hash值是否相同且当前的key和Entry里面的key是否相同
37             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
38                 //如果相同
39                 //将以前的value 赋值给 oldValue这个局部变量
40                 V oldValue = e.value;
41                 //当前的value赋盖原本已有值的value
42                 e.value = value;
43                 //一个空的预留方法,可以重写
44                 e.recordAccess(this);
45                 //返回老值
46                 return oldValue;
47             }
48         }
49         //如果没有相同的key则修改次数+1
50         modCount++;
51         //将当前元素加入到新的Entry里面,存入table[i]中,元素数量+1
52         addEntry(hash, key, value, i);
53         //如果是一个新key都会返回null
54         return null;
55     }
View Code

解读put(K,V)中 putForNullKey(value) , indexFor(hash,table.length)  , addEntry(hash,key,value,i)

 

  1 /**
  2      * Offloaded version of put for null keys
  3      */
  4     //put的key为NULL
  5     private V putForNullKey(V value) {
  6         //先去查找table[0]中是否有值
  7         for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  8             //table[0]中的Entry key 为null 时
  9             if (e.key == null) {
 10                 //这里和put里面的处理重复key,覆盖value
 11                 //将当前值赋值给一个局部变量
 12                 V oldValue = e.value;
 13                 //新的值覆盖老的值
 14                 e.value = value;
 15                 //空方法
 16                 e.recordAccess(this);
 17                 //返回以前的值
 18                 return oldValue;
 19             }
 20         }
 21         //如果table[0]没有元素时 修改次数+1
 22         modCount++;
 23         //新建一个Entry将它放入table[0]中
 24         addEntry(0, null, value, 0);
 25         //返回null
 26         return null;
 27     }
 28 
 29  /**
 30      * A randomizing value associated with this instance that is applied to
 31      * hash code of keys to make hash collisions harder to find. If 0 then
 32      * alternative hashing is disabled.
 33      */
 34     //hash种子
 35     transient int hashSeed = 0;
 36 
 37  /**
 38      * Retrieve object hash code and applies a supplemental hash function to the
 39      * result hash, which defends against poor quality hash functions.  This is
 40      * critical because HashMap uses power-of-two length hash tables, that
 41      * otherwise encounter collisions for hashCodes that do not differ
 42      * in lower bits. Note: Null keys always map to hash 0, thus index 0.
 43      */
 44     //生成哈希码
 45     final int hash(Object k) {
 46         //hash种子,在第一次put的时候会生成一次 下面的算法有兴趣的可以看看
 47         int h = hashSeed;
 48         if (0 != h && k instanceof String) {
 49             return sun.misc.Hashing.stringHash32((String) k);
 50         }
 51 
 52         h ^= k.hashCode();
 53 
 54         // This function ensures that hashCodes that differ only by
 55         // constant multiples at each bit position have a bounded
 56         // number of collisions (approximately 8 at default load factor).
 57         h ^= (h >>> 20) ^ (h >>> 12);
 58         return h ^ (h >>> 7) ^ (h >>> 4);
 59     }
 60 
 61 
 62 
 63 
 64  /**
 65      * Returns index for hash code h.
 66      */
 67     //根据hash值和table的容量计算出一个下标
 68     static int indexFor(int h, int length) {
 69         // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
 70         return h & (length-1);
 71     }
 72 
 73 
 74  /**
 75      * Adds a new entry with the specified key, value and hash code to
 76      * the specified bucket.  It is the responsibility of this
 77      * method to resize the table if appropriate.
 78      *
 79      * Subclass overrides this to alter the behavior of put method.
 80      */
 81     //这里比较关键了,细心的朋友可能看到了当key为空的时候传入进来的值
 82     //(0,null,value,0)
 83     void addEntry(int hash, K key, V value, int bucketIndex) {
 84         //如果元素个数>=容量 且 当前出现碰撞
 85         if ((size >= threshold) && (null != table[bucketIndex])) {
 86             //扩容
 87             resize(2 * table.length);
 88             //如果当前key不为null则hash(key) 否则 hash为0,这里和上面key为0时hash保持一致
 89             hash = (null != key) ? hash(key) : 0;
 90             //获取一个下标覆盖原有下标
 91             bucketIndex = indexFor(hash, table.length);
 92         }
 93         //创建一个Entry存入table[bucketIndex]中,元素+1
 94         createEntry(hash, key, value, bucketIndex);
 95     }
 96 
 97 
 98 
 99 
100  /**
101      * Like addEntry except that this version is used when creating entries
102      * as part of Map construction or "pseudo-construction" (cloning,
103      * deserialization).  This version needn't worry about resizing the table.
104      *
105      * Subclass overrides this to alter the behavior of HashMap(Map),
106      * clone, and readObject.
107      */
108     //创建一个Entry
109     void createEntry(int hash, K key, V value, int bucketIndex) {
110         //这里会将当前下标(bucketIndex)的table里的值放入局部类e中
111         //这里的操作是为了解决碰撞具体往下面看new Entry
112         Entry<K,V> e = table[bucketIndex];
113         //创建一个新的Entry放入当前下标中
114         table[bucketIndex] = new Entry<>(hash, key, value, e);
115         //元素+1
116         size++;
117     }
118 
119 //一个静态的内部类 实现了 map.Entry接口
120 //这里只解释下碰撞处理过程,具体里面的内部类方法有兴趣可以自己看下很简单
121  static class Entry<K,V> implements Map.Entry<K,V> {
122         final K key;
123         V value;
124         Entry<K,V> next;
125         int hash;
126 
127         /**
128          * Creates new entry.
129          */
130         //上面我们看到new实现了4个参数的构造,里面接收了一个局部类是做什么用的呢我们往下看
131         Entry(int h, K k, V v, Entry<K,V> n) {
132             //将新的value赋值给当前类的value
133             value = v;
134             //划重点----------------------------------
135             //这里是老的entry赋值给新的entry的next
136             //所以从这里我们可以得出key的hash发生冲突后不能说覆盖以前的值
137             //以前的值要想获取就在新的值的next里面
138             //他们的hash相同key不相同所以在上面源码我们可以发现只有当hash相同时才会进行碰撞处理
139             //因为下标是 hash 和 表容量 计算出来的
140             //注:没有发生碰撞时next为null
141             next = n;
142             //新的key
143             key = k;
144             //新的hash值
145             hash = h;
146         }
147 
148 }
View Code

 

 

 

以上就是put需要执行的一些动作

下面咱们进入扩容方法 resize(2*table.length)

 1 //扩容传入一个新的容量
 2 void resize(int newCapacity) {
 3         //将当前table放入一个局部数组变量中
 4         Entry[] oldTable = table;
 5         //获得当前table的大小
 6         int oldCapacity = oldTable.length;
 7         //如果当前容量为1<<30=2^30 时
 8         if (oldCapacity == MAXIMUM_CAPACITY) {
 9             //当前的扩容因子大小赋值为2^31-1
10             threshold = Integer.MAX_VALUE;
11             //高并发的时候会进入,隐患
12             //划重点---------------------
13             //如果一直高并发会出大问题,譬如数组越界
14             return;
15         }
16         //创建一个新的table,容量为2*table.length 记住一定是2倍
17         Entry[] newTable = new Entry[newCapacity];
18         //创建一个新的hash种子,将以前的值放入newTable中
19         transfer(newTable, initHashSeedAsNeeded(newCapacity));
20         //扩容后的table覆盖现在的table
21         //来,这里有个坑,划重点--------------
22         //如果现在很多线程在get 这里会怎样?
23         //对应的value全为null
24         table = newTable;
25         //将扩容因子赋值为倆数中小的那一个
26         threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
27     }
28 
29 
30 
31 //老的数据迁移到新table里面
32  void transfer(Entry[] newTable, boolean rehash) {
33         //新的表的容量赋值给一个局部变量
34         int newCapacity = newTable.length;
35         //循环以前的table,这里还没进行赋值所以table是以前的数据
36         for (Entry<K,V> e : table) {
37             while(null != e) {
38                 //将entry里面的next赋值给局部类
39                 Entry<K,V> next = e.next;
40                 if (rehash) {
41                     //如果是新的hash种子,生成新的hash值
42                     e.hash = null == e.key ? 0 : hash(e.key);
43                 }
44                 //获取一个新的table的index
45                 int i = indexFor(e.hash, newCapacity);
46                 //将当前index下的table赋值给当前entry.next
47                 e.next = newTable[i];
48                 //将当前老table中的数据放入新table当前下标中
49                 newTable[i] = e;
50                 //将局部变量的值赋值给局部变量e
51                 //while循环依据,主要是保存碰撞元素中next的值
52                 //这里建议写自己个例子帮助理解,仿Entry类就好
53                 e = next;
54             }
55         }
56     }
View Code

至此put(K,V)结束了,扩容也一并在里面讲解了

梳理一下:

  1.第一次put值的时候初始化表空间

  2.key为空的时候会加入

  3.会根据key生成哈希值

  4.如果发生碰撞(hash值相等)的时候,会将新的值存放在新的entry里,老的值会存放在新值的entry.next里

  5.正常情况下 修改次数+1,元素数量+1,新建一个Entry放入table[bucketIndex]中

  6.bucketIndex是根据hash值和当前table长度计算出来的

  7.如果遇到扩容,新容器是旧容器的2倍,新的容器将重新生成hash种子,老元素会赋值到新容器中,注意:高并发的时候取值可能为null,严重时会出现数组越界,死循环的问题,所以HashMap是线程不安全的

 

下面咱们看看get(K)

 1  
 2 //根据key获取value
 3 public V get(Object key) {
 4         //如果key为null,则去table[0]上的值
 5         if (key == null)
 6             return getForNullKey();
 7         //根据key获取value
 8         Entry<K,V> entry = getEntry(key);
 9         //如果entry不为null则返回当前entry的value
10         return null == entry ? null : entry.getValue();
11     }
12 
13 //获取key为null的value
14  private V getForNullKey() {
15         //判断元素个数为0返回null
16         if (size == 0) {
17             return null;
18         }
19         //如果table[0]上有元素则返回当前table[0]的value
20         for (Entry<K,V> e = table[0]; e != null; e = e.next) {
21             if (e.key == null)
22                 return e.value;
23         }
24         //否则返回null
25         return null;
26     }
27 
28 
29 //key不为null时
30 final Entry<K,V> getEntry(Object key) {
31         //判断元素个数为0返回null
32         if (size == 0) {
33             return null;
34         }
35         //根据key获取hash值
36         int hash = (key == null) ? 0 : hash(key);
37         //通过hash和table容量取出下标获取当前下标table的entry
38         for (Entry<K,V> e = table[indexFor(hash, table.length)];
39              e != null;
40              e = e.next) {
41             Object k;
42             //如果hash值和key都相等则返回对应的entry
43             if (e.hash == hash &&
44                 ((k = e.key) == key || (key != null && key.equals(k))))
45                 return e;
46         }
47         //否则返回空
48         return null;
49     }
View Code

get相对来说就简单很多了!

咱们来继续来看看remove操作

 1 //移除key对应table[]中的entry
 2 public V remove(Object key) {
 3         //移除key对应table[]中的entry
 4         Entry<K,V> e = removeEntryForKey(key);
 5         return (e == null ? null : e.value);
 6     }
 7 
 8 //移除key对应table[]中的entry
 9 final Entry<K,V> removeEntryForKey(Object key) {
10         //元素个数为0返回null
11         if (size == 0) {
12             return null;
13         }
14         //判断hash是否为null
15         int hash = (key == null) ? 0 : hash(key);
16         //生成table的index
17         int i = indexFor(hash, table.length);
18         //将当前下标的entry放在局部变量中
19         Entry<K,V> prev = table[i];
20         //局部变量pre赋值给局部变量e
21         Entry<K,V> e = prev;
22         //如果当前元素不为空
23         while (e != null) {
24             //先取出当前元素的next
25             Entry<K,V> next = e.next;
26             Object k;
27             //判断hash值和key是否相等,注意碰撞只是hash值相同key不同
28             if (e.hash == hash &&
29                 ((k = e.key) == key || (key != null && key.equals(k)))) {
30                 //相等修改次数+1
31                 modCount++;
32                 //元素-1
33                 size--;
34                 //一般情况下两个内存地址是相等的
35                 if (prev == e)
36                     //将next的值覆盖当前下标的值,没有碰撞时这时候table[i]当前值为null
37                     table[i] = next;
38                 else
39                     //碰撞情况下将next的值覆盖pre.next也就是说这时候prev是有值的,移除的只是相同hashcode但key不同的碰撞体
40                     prev.next = next;
41                 //空方法
42                 e.recordRemoval(this);
43                 //返回移除的entry
44                 return e;
45             }
46             //我们上面知道了碰撞后旧值在新值的next中所以这一段理解就是如果当前下标中有碰撞存在那么将e赋值给prev
47             prev = e;
48             //e等于当前e.next,如果不为空则继续进行移除
49             e = next;
50         }
51         //e为null直接返回
52         return e;
53     }
View Code

清除HashMap

 1 //清除hashmap 
 2 public void clear() {
 3         //修改+1
 4         modCount++;
 5         //循环table将每个下标对应的值都替换成null
 6         Arrays.fill(table, null);
 7         //元素个数归零
 8         size = 0;
 9     }
10 //循环table将每个下标对应的值都替换成null
11  public static void fill(Object[] a, Object val) {
12         for (int i = 0, len = a.length; i < len; i++)
13             a[i] = val;
14     }
View Code

 

我们注意到主要传入key那么就一定会进行hash然后通过hash和表容量计算出对应table的index也可以说bucketIndex.这个流程一定要注意

 

以上就是基本的HashMap源码解读了,有讲解不清楚,容易混淆的文字或者错误的地方欢迎指出.

 

posted @ 2018-06-28 11:10  SugarWater  阅读(361)  评论(0编辑  收藏  举报