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 }
以上是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;
以上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 }
解读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 }
以上就是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 }
至此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 }
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 }
清除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 }
我们注意到主要传入key那么就一定会进行hash然后通过hash和表容量计算出对应table的index也可以说bucketIndex.这个流程一定要注意
以上就是基本的HashMap源码解读了,有讲解不清楚,容易混淆的文字或者错误的地方欢迎指出.