• 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源码解读了,有讲解不清楚,容易混淆的文字或者错误的地方欢迎指出.

  • 相关阅读:
    Python PEP—Python增强提案
    容器技术介绍:Docker简介及安装
    Python笔记:List相关操作
    Python笔记:字符串操作
    Python笔记:属性值设置和判断变量是否存在
    mitmproxy 代理工具介绍:rewrite和map local实现
    接口测试代理工具charles mock测试
    接口测试框架Requests
    JMeter性能测试:JMeter安装及脚本录制回放
    PHP Parse error: parse error, unexpected T_OBJECT_OPERATOR
  • 原文地址:https://www.cnblogs.com/kuanglongblogs/p/9234691.html
Copyright © 2020-2023  润新知