• Java 集合:(二十) HashMap 源码剖析(JDK7)


    一、HashMap 中的成员变量  

      

        成员变量说明:

     1 ① 默认初始化容量 16(必须为2的次幂)
     2 /**
     3  * The default initial capacity - MUST be a power of two.
     4  */
     5 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
     6 
     7 ② 最大容量  1<<30 == 2^30
     8 /**
     9  * The maximum capacity, used if a higher value is implicitly specified
    10  * by either of the constructors with arguments.
    11  * MUST be a power of two <= 1<<30.
    12  */
    13 static final int MAXIMUM_CAPACITY = 1 << 30;
    14 
    15 ③ 默认加载因子  0.75 (在构造函数中未指定时使用的加载系数)
    16 /**
    17  * The load factor used when none specified in constructor.
    18  */
    19 static final float DEFAULT_LOAD_FACTOR = 0.75f;
    20 
    21 ④ 一个空的 Empty 的数组 (当表未膨胀时要共享的空表实例)
    22 /**
    23  * An empty table instance to share when the table is not inflated.
    24  */
    25 static final Entry<?,?>[] EMPTY_TABLE = {};
    26 
    27 ⑤ 存储数据的数组,并且 table 的长度必须是2的次幂
    28 /**
    29  * The table, resized as necessary. Length MUST Always be a power of two.
    30  */
    31 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
    32 
    33 ⑥ Map 中元素的个数
    34 /**
    35  * The number of key-value mappings contained in this map.
    36  */
    37 transient int size;
    38 
    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 int threshold;
    47 
    48 ⑧ 负载因子
    49 /**
    50  * The load factor for the hash table.
    51  *
    52  * @serial
    53  */
    54 final float loadFactor;
    55 
    56 ⑨ 记录对该 HashMap 进行结构修改的次数,用于快速失败(fail-fast)
    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 transient int modCount;
    65 
    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 static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
    78 
    79 11  备用哈希
    80 /**
    81  * A randomizing value associated with this instance that is applied to
    82  * hash code of keys to make hash collisions harder to find. If 0 then
    83  * alternative hashing is disabled.
    84  */
    85 transient int hashSeed = 0;
    86 
    87 
    88 12 对整个HashMap的映射视图
    89 // Views
    90 private transient Set<Map.Entry<K,V>> entrySet = null;
    91 
    92 13 标识该类的 序列化ID
    93 private static final long serialVersionUID = 362498820763181265L;

    二、HashMap 的构造器

        HashMap 提供了四个构造器,可以分为两类,下面进行学习:

      1、无参构造器

    1 public HashMap() {
    2     this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    3 }

        在这里使用默认容量(16)和默认加载因子(0.75)调用本类构造器。

      2、指定容量的构造器

    1 public HashMap(int initialCapacity) {
    2     this(initialCapacity, DEFAULT_LOAD_FACTOR);
    3 }

        这里使用了指定的初始容量,仍然采用默认的加载因子。

      3、指定容量和负载因子

     1 public HashMap(int initialCapacity, float loadFactor) {
     2     if (initialCapacity < 0)
     3         throw new IllegalArgumentException("Illegal initial capacity: " +
     4                                            initialCapacity);
     5     if (initialCapacity > MAXIMUM_CAPACITY)
     6         initialCapacity = MAXIMUM_CAPACITY;
     7     if (loadFactor <= 0 || Float.isNaN(loadFactor))
     8         throw new IllegalArgumentException("Illegal load factor: " +
     9                                            loadFactor);
    10 
    11     this.loadFactor = loadFactor;
    12     threshold = initialCapacity;
    13     init();
    14 }

        该方法使用传递进来的容量和加载因子进行初始化,对两个成员变量进行了赋值。

    void init() {}
    

          并且 init() 方法中也为给数组分配初始空间,那什么时候才会给数组进行内存的分配呢?

        来看一下 put() 方法:

          

         当我们第一次调用 put() 方法时, table 还是空数组,就会执行 inflateTable() 方法,

     1 /**
     2  * Inflates the table.
     3  */
     4 private void inflateTable(int toSize) {
     5     // Find a power of 2 >= toSize
     6     int capacity = roundUpToPowerOf2(toSize);
     7 
     8     threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
     9     table = new Entry[capacity];
    10     initHashSeedAsNeeded(capacity);
    11 }
    12 
    13 private static int roundUpToPowerOf2(int number) {
    14     // assert number >= 0 : "number must be non-negative";
    15     return number >= MAXIMUM_CAPACITY
    16             ? MAXIMUM_CAPACITY
    17             : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    18 }

         在这里就会为 table 分配一个 capacity 大小的空间,并且保证了 capacity 是 2的次幂

        注意:在 JDK1.7 较低的版本中,会在构造器中直接为 table 分配内存(下面是JDK1.7.0_7版本),在高版本中(趋于JDK1.8)会在put() 方法中为 table 指定容量大小。【可以理解为JDK7 与 JDK8 的区别点】

     1 public HashMap(int initialCapacity, float loadFactor) {
     2     if (initialCapacity < 0)
     3         throw new IllegalArgumentException("Illegal initial capacity: " +
     4                                            initialCapacity);
     5     if (initialCapacity > MAXIMUM_CAPACITY)
     6         initialCapacity = MAXIMUM_CAPACITY;
     7     if (loadFactor <= 0 || Float.isNaN(loadFactor))
     8         throw new IllegalArgumentException("Illegal load factor: " +
     9                                            loadFactor);
    10 
    11     // Find a power of 2 >= initialCapacity
    12     int capacity = 1;
    13     while (capacity < initialCapacity)
    14         capacity <<= 1;
    15 
    16     this.loadFactor = loadFactor;
    17     threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    18     table = new Entry[capacity];
    19     useAltHashing = sun.misc.VM.isBooted() &&
    20             (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    21     init();
    22 }

      4、传入 Map 的构造器

    1 public HashMap(Map<? extends K, ? extends V> m) {
    2     this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
    3                   DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
    4     inflateTable(threshold);
    5 
    6     putAllForCreate(m);
    7 }

        可以通过传入一个 Map 来创建HashMap 集合。主要做了两件事:① 扩充数组;② 把参数Map的节点加入到 HashMap中;

     1 private void putAllForCreate(Map<? extends K, ? extends V> m) {
     2     //遍历参数的map所有的 entrySet(),并根据entrySet() 的 key-value来构建对应节点
     3     for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
     4         putForCreate(e.getKey(), e.getValue());
     5 }
     6 
     7 private void putForCreate(K key, V value) {
     8     int hash = null == key ? 0 : hash(key);   //根据 key计算 hash值
     9     int i = indexFor(hash, table.length);     //根据 key 的 hash值计算在数组中的下标位置
    10 
    11     /**
    12      * Look for preexisting entry for key.  This will never happen for
    13      * clone or deserialize.  It will only happen for construction if the
    14      * input Map is a sorted map whose ordering is inconsistent w/ equals.
    15      */
    16      /**
    17         ① 根据下标获取对应的元素e,判断是否为空
    18             ② 如果 e 不为空,比较要添加元素的hash值和 e 的hash值是否一样
    19             ③ 如果 e 和要添加元素的 hash 值一样,则进一步进行==比较和 equals()比较
    20             ④ 如果 hash值 并且它们的 key 的值也一样,则用新值替代旧值(覆盖操作) 
    21         ⑤ 直到 e 为空,执行下面的 createEntry()
    22      */
    23     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    24         Object k;
    25         if (e.hash == hash &&
    26             ((k = e.key) == key || (key != null && key.equals(k)))) {
    27             e.value = value;
    28             return;
    29         }
    30     }
    31     //如果当前位置为 null,创建Entry
    32     createEntry(hash, key, value, i);
    33 }
    34 
    35 
    36 void createEntry(int hash, K key, V value, int bucketIndex) {
    37     //获取当前数组下标中的 元素 e
    38     Entry<K,V> e = table[bucketIndex];
    39     //创建一个新的元素节点,并且它的下一个元素指向 e,同时把新建节点放在数组中
    40     table[bucketIndex] = new Entry<>(hash, key, value, e);
    41     //元素数量加1
    42     size++;
    43 }

    三、HashMap 中的节点

      JDK中存放的是 Entry 类型的节点,它继承了 Map接口中的 Entry类型,Entry 类型声明了四个成员变量,并且其中有一个为 Entry 类型的 next,为该节点所指向的下一个节点,形成了一个单向链表的数据结构。

     1 static class Entry<K,V> implements Map.Entry<K,V> {
     2     final K key;         //当前节点的 key
     3     V value;             //当前节点的 value
     4     Entry<K,V> next;     //当前节点所指向的下一个节点
     5     int hash;            //当前节点的 hash 值
     6 
     7     /**
     8      * Creates new entry.
     9      */
    10     Entry(int h, K k, V v, Entry<K,V> n) {
    11         value = v;
    12         next = n;
    13         key = k;
    14         hash = h;
    15     }
    16 
    17     public final K getKey() {
    18         return key;
    19     }
    20 
    21     public final V getValue() {
    22         return value;
    23     }
    24 
    25     public final V setValue(V newValue) {
    26         V oldValue = value;
    27         value = newValue;
    28         return oldValue;
    29     }
    30 
    31     public final boolean equals(Object o) {
    32         if (!(o instanceof Map.Entry))
    33             return false;
    34         Map.Entry e = (Map.Entry)o;
    35         Object k1 = getKey();
    36         Object k2 = e.getKey();
    37         if (k1 == k2 || (k1 != null && k1.equals(k2))) {
    38             Object v1 = getValue();
    39             Object v2 = e.getValue();
    40             if (v1 == v2 || (v1 != null && v1.equals(v2)))
    41                 return true;
    42         }
    43         return false;
    44     }
    45 
    46     public final int hashCode() {
    47         return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
    48     }
    49 
    50     public final String toString() {
    51         return getKey() + "=" + getValue();
    52     }
    53 
    54     /**
    55      * This method is invoked whenever the value in an entry is
    56      * overwritten by an invocation of put(k,v) for a key k that's already
    57      * in the HashMap.
    58      */
    59     void recordAccess(HashMap<K,V> m) {
    60     }
    61 
    62     /**
    63      * This method is invoked whenever the entry is
    64      * removed from the table.
    65      */
    66     void recordRemoval(HashMap<K,V> m) {
    67     }
    68 }

    四、HashMap 中 table 的分配

      HashMap 中为 table 分配空间执行 inflateTable() 方法,会根据 参数toSize 计算大于等于 toSize的最大2的整数次幂(后面解释会为什么)

     1 /**
     2  * Inflates the table.
     3  */
     4 private void inflateTable(int toSize) {
     5     // Find a power of 2 >= toSize
     6     int capacity = roundUpToPowerOf2(toSize);  //确保是 2的次幂
     7 
     8     threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
     9     table = new Entry[capacity];
    10     initHashSeedAsNeeded(capacity);
    11 }
    12 
    13 private static int roundUpToPowerOf2(int number) {
    14     // assert number >= 0 : "number must be non-negative";
    15     return number >= MAXIMUM_CAPACITY
    16             ? MAXIMUM_CAPACITY
    17             : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    18 }

      其中会调用 Integer 的 hightestOneBit() 方法来计算2的整数次幂:

    1 public static int highestOneBit(int i) {
    2     // HD, Figure 3-1
    3     i |= (i >>  1);
    4     i |= (i >>  2);
    5     i |= (i >>  4);
    6     i |= (i >>  8);
    7     i |= (i >> 16);
    8     return i - (i >>> 1);
    9 }

    五、hash 函数

      HashMap 中通过异或和无符号右移来尽可能的打乱 key 的 hashCode(),增加hashCode 的随机性,从而来降低 Hash碰撞,使得元素分布更加均匀。

     1 final int hash(Object k) {
     2     int h = hashSeed;   //hashSeed = 0;
     3     if (0 != h && k instanceof String) {
     4         return sun.misc.Hashing.stringHash32((String) k);
     5     }
     6 
     7     h ^= k.hashCode();
     8 
     9     // This function ensures that hashCodes that differ only by
    10     // constant multiples at each bit position have a bounded
    11     // number of collisions (approximately 8 at default load factor).
    12     h ^= (h >>> 20) ^ (h >>> 12);
    13     return h ^ (h >>> 7) ^ (h >>> 4);
    14 }

    六、计算元素在数组的下标

      在这里用了一个巧妙的算法, hash & (length - 1) == hash % length,而这一算法的前提就是保证 length 是2的整数次幂,而且&运算比 % 的效率要高。(后面有详解)

    1 /**
    2  * Returns index for hash code h.
    3  */
    4 static int indexFor(int h, int length) {
    5     // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    6     return h & (length-1);
    7 }

    七、添加元素 put() 系列

      1、添加单个元素

     1 public V put(K key, V value) {
     2     // 如果 table 是空数组,则进行数组的扩充
     3     if (table == EMPTY_TABLE) {
     4         inflateTable(threshold);
     5     }
     6     //如果添加的 key 是 null,进行空值的节点的添加
     7     if (key == null)
     8         return putForNullKey(value);
     9         
    10     //根据 key 计算 哈希值
    11     int hash = hash(key);
    12     
    13     //根据 哈希值 计算key 所在数组下标
    14     int i = indexFor(hash, table.length);
    15     /**
    16         1) 获取当前下标所在元素 e,判断 e 是否为 null:
    17             1.1)如果 e 不为 null,则用e的hash值与添加元素hash比较:
    18                 1.2)如果 hash值不一样,进行下一个元素的 hash值判断[因为有可能是一个链表],如果都不一样,进行节点添加
    19                 1.3)如果 hash值一样,则进行两个元素的 key 进行比较:
    20                     1.4)如果两个元素的 hash值 和 equals 都一样,则用新值替换旧值,并返回旧值;
    21                     1.5)如果 hash值 一样,key 值不一样,则进行下一个元素的比较
    22             
    23         2) 如果 e 直接为 null,或者遍历完链表后没有找到可覆盖的元素,则进行节点的添加
    24     */
    25     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    26         Object k;
    27         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    28             V oldValue = e.value;
    29             e.value = value;
    30             e.recordAccess(this);
    31             return oldValue;
    32         }
    33     }
    34 
    35     modCount++;
    36     //添加节点
    37     addEntry(hash, key, value, i);
    38     return null;
    39 }
    40 
    41 
    42 //进行 null 键的添加,null为 key 放在索引为 0 的位置,
    43 /**
    44  * Offloaded version of put for null keys
    45  */
    46 private V putForNullKey(V value) {
    47     for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    48         if (e.key == null) {
    49             V oldValue = e.value;
    50             e.value = value;
    51             e.recordAccess(this);
    52             return oldValue;
    53         }
    54     }
    55     modCount++;
    56     addEntry(0, null, value, 0);
    57     return null;
    58 }
    59 
    60 //添加节点
    61 void addEntry(int hash, K key, V value, int bucketIndex) {
    62     //如果元素达到了 临界点(threshold)并且数组中当前索引不为 null,进行扩容
    63     if ((size >= threshold) && (null != table[bucketIndex])) {
    64         //扩容为原来的 2 倍
    65         resize(2 * table.length);
    66         //重新计算 key 的hash值
    67         hash = (null != key) ? hash(key) : 0;
    68         //重新计算所在索引
    69         bucketIndex = indexFor(hash, table.length);
    70     }
    71     //创建节点
    72     createEntry(hash, key, value, bucketIndex);
    73 }
    74 
    75 //创建节点
    76 void createEntry(int hash, K key, V value, int bucketIndex) {
    77     //获取当前索引的元素 e
    78     Entry<K,V> e = table[bucketIndex];
    79     // 新建一个节点,新节点的下一个节点指向 e,并把新节点放在数组中
    80     table[bucketIndex] = new Entry<>(hash, key, value, e);
    81     //元素个数加1
    82     size++;
    83 }

       总结:

       添加步骤:

      (1)调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置;

      (2)如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1

      (3)如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:

          ① 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2

          ② 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:

            a、如果equals()返回false:此时key1-value1添加成功。----情况3

            b、如果equals()返回true:使用value1替换value2。

      注意:    

        (1)对于情况2和情况3:此时 key1-value1 和原来的数据以链表的方式存储;

        (2)在添加的时候,链表会以 头插法 进行插入;

        (3)在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来;

      2、添加一个 Map

     1 public void putAll(Map<? extends K, ? extends V> m) {
     2     int numKeysToBeAdded = m.size();
     3     if (numKeysToBeAdded == 0)
     4         return;
     5 
     6     //进行数组的初始化
     7     if (table == EMPTY_TABLE) {
     8         inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));
     9     }
    10 
    11     /*
    12      * Expand the map if the map if the number of mappings to be added
    13      * is greater than or equal to threshold.  This is conservative; the
    14      * obvious condition is (m.size() + size) >= threshold, but this
    15      * condition could result in a map with twice the appropriate capacity,
    16      * if the keys to be added overlap with the keys already in this map.
    17      * By using the conservative calculation, we subject ourself
    18      * to at most one extra resize.
    19      */
    20      //计算容量,确保数组够用
    21     if (numKeysToBeAdded > threshold) {
    22         int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
    23         if (targetCapacity > MAXIMUM_CAPACITY)
    24             targetCapacity = MAXIMUM_CAPACITY;
    25         int newCapacity = table.length;
    26         while (newCapacity < targetCapacity)
    27             newCapacity <<= 1;
    28         if (newCapacity > table.length)
    29             resize(newCapacity);
    30     }
    31     
    32     //遍历 参数的 entrySet,一个个添加到 该 Map中
    33     for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
    34         put(e.getKey(), e.getValue());
    35 }

      可以看到核心方法是 put()方法,并且里面做了很多的事情。

    八、扩容

       HashMap 什么时候会进行扩容呢?在执行 put() 方法时,等到给table数组添加元素之前,会进行判断,当元素个数达到临界点并且要添加的数组位置没有元素时,进行扩容,扩容为原来的2倍。

     1 public V put(K key, V value) {
     2     if (table == EMPTY_TABLE) {
     3         inflateTable(threshold);
     4     }
     5     if (key == null)
     6         return putForNullKey(value);
     7     int hash = hash(key);
     8     int i = indexFor(hash, table.length);
     9     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    10         Object k;
    11         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    12             V oldValue = e.value;
    13             e.value = value;
    14             e.recordAccess(this);
    15             return oldValue;
    16         }
    17     }
    18 
    19     modCount++;
    20     addEntry(hash, key, value, i);
    21     return null;
    22 }
    23 
    24 void addEntry(int hash, K key, V value, int bucketIndex) {
    25     if ((size >= threshold) && (null != table[bucketIndex])) {
    26         //扩容为原来数组的2倍
    27         resize(2 * table.length);
    28         //重新计算当前要添加元素key的 hash值
    29         hash = (null != key) ? hash(key) : 0;
    30         bucketIndex = indexFor(hash, table.length);
    31     }
    32 
    33     createEntry(hash, key, value, bucketIndex);
    34 }

       HashMap 的扩容方法:扩容为原来的2倍,如果达到了最大容量,则无需扩容。否则创建一个新数组,将原数组中的元素重新计算hash值,判断在新数组中的位置从而添加到新数组中。

     1 void resize(int newCapacity) {
     2     Entry[] oldTable = table;
     3     int oldCapacity = oldTable.length;
     4     //如果已经达到最大值,则不需要考虑扩容
     5     if (oldCapacity == MAXIMUM_CAPACITY) {
     6         threshold = Integer.MAX_VALUE;
     7         return;
     8     }
     9     
    10     //创建一个新数组
    11     Entry[] newTable = new Entry[newCapacity];
    12     //将原数组中的元素移动到新数组中
    13     transfer(newTable, initHashSeedAsNeeded(newCapacity));
    14     //重复赋值给成员变量 table
    15     table = newTable;
    16     //重新计算临界点
    17     threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    18 }
    19 
    20 
    21 void transfer(Entry[] newTable, boolean rehash) {
    22     int newCapacity = newTable.length;
    23     for (Entry<K,V> e : table) {
    24         while(null != e) {
    25             Entry<K,V> next = e.next;
    26             //是否需要重新 hash值的计算
    27             if (rehash) {
    28                 e.hash = null == e.key ? 0 : hash(e.key);
    29             }
    30             int i = indexFor(e.hash, newCapacity);
    31             e.next = newTable[i];
    32             newTable[i] = e;
    33             e = next;
    34         }
    35     }
    36 }

    九、查找元素 get() 系列

       HashMap可以根据指定的 key 来查找对应的 value。

     1 //根据 key 来获取 value
     2 public V get(Object key) {
     3     if (key == null)
     4         return getForNullKey();
     5     Entry<K,V> entry = getEntry(key);
     6 
     7     //根据 entry 来获取 value
     8     return null == entry ? null : entry.getValue();
     9 }
    10 
    11 //根据 null 来获取 value
    12 private V getForNullKey() {
    13     if (size == 0) {
    14         return null;
    15     }
    16     for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    17         if (e.key == null)
    18             return e.value;
    19     }
    20     return null;
    21 }
    22 
    23 //根据 key 来获取 Entry
    24 final Entry<K,V> getEntry(Object key) {
    25     if (size == 0) {
    26         return null;
    27     }
    28 
    29     //通过 key 的 hash 值计算数组中的下标
    30     int hash = (key == null) ? 0 : hash(key); 
    31     
    32     for (Entry<K,V> e = table[indexFor(hash, table.length)];
    33          e != null;
    34          e = e.next) {
    35         Object k;
    36         if (e.hash == hash &&
    37             ((k = e.key) == key || (key != null && key.equals(k))))
    38             return e;
    39     }
    40     return null;
    41 }

    十、删除元素 remove() 系列

       HashMap 可以根据指定的 key 来从集合中移除元素Entry,并且将已删除的元素返回。

     1 //根据 key 移除元素,如果元素删除掉,返回已删除元素的 value值
     2 public V remove(Object key) {
     3     Entry<K,V> e = removeEntryForKey(key);
     4     return (e == null ? null : e.value);
     5 }
     6 
     7 //根据 key 移除 entry,并返回已移除的 entry
     8 final Entry<K,V> removeEntryForKey(Object key) {
     9     if (size == 0) {
    10         return null;
    11     }
    12     int hash = (key == null) ? 0 : hash(key);
    13     int i = indexFor(hash, table.length);
    14     Entry<K,V> prev = table[i];
    15     Entry<K,V> e = prev;
    16 
    17     while (e != null) {
    18         Entry<K,V> next = e.next;
    19         Object k;
    20         if (e.hash == hash &&
    21             ((k = e.key) == key || (key != null && key.equals(k)))) {
    22             modCount++;
    23             size--;
    24             if (prev == e)
    25                 table[i] = next;
    26             else
    27                 prev.next = next;
    28             e.recordRemoval(this);
    29             return e;
    30         }
    31         prev = e;
    32         e = next;
    33     }
    34 
    35     return e;
    36 }

    十一、克隆与序列化

      1、克隆

         HashMap 实现了 Cloneable 接口,表示支持克隆。

     1 public Object clone() {
     2     HashMap<K,V> result = null;
     3     try {
     4         result = (HashMap<K,V>)super.clone();
     5     } catch (CloneNotSupportedException e) {
     6         // assert false;
     7     }
     8     if (result.table != EMPTY_TABLE) {
     9         result.inflateTable(Math.min(
    10             (int) Math.min(
    11                 size * Math.min(1 / loadFactor, 4.0f),
    12                 // we have limits...
    13                 HashMap.MAXIMUM_CAPACITY),
    14            table.length));
    15     }
    16     result.entrySet = null;
    17     result.modCount = 0;
    18     result.size = 0;
    19     result.init();
    20     result.putAllForCreate(this);
    21 
    22     return result;
    23 }

      2、序列化和反序列化

        HashMap 实现了 Serializable 接口,支持序列化和反序列化,同时使用 transient 修饰table 和 size,这样就只会序列化存有元素的部分数组,从而不序列化整个数组,节省了时间和空间,提高了效率。

     1 transient int size;
     2 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
     3 
     4 //序列化
     5 private void writeObject(java.io.ObjectOutputStream s)
     6     throws IOException
     7 {
     8     // Write out the threshold, loadfactor, and any hidden stuff
     9     s.defaultWriteObject();
    10 
    11     // Write out number of buckets
    12     if (table==EMPTY_TABLE) {
    13         s.writeInt(roundUpToPowerOf2(threshold));
    14     } else {
    15        s.writeInt(table.length);
    16     }
    17 
    18     // Write out size (number of Mappings)
    19     s.writeInt(size);
    20 
    21     // Write out keys and values (alternating)
    22     if (size > 0) {
    23         for(Map.Entry<K,V> e : entrySet0()) {
    24             s.writeObject(e.getKey());
    25             s.writeObject(e.getValue());
    26         }
    27     }
    28 }
    29 
    30 //反序列化
    31 private void readObject(java.io.ObjectInputStream s)
    32      throws IOException, ClassNotFoundException
    33 {
    34     // Read in the threshold (ignored), loadfactor, and any hidden stuff
    35     s.defaultReadObject();
    36     if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
    37         throw new InvalidObjectException("Illegal load factor: " +
    38                                            loadFactor);
    39     }
    40 
    41     // set other fields that need values
    42     table = (Entry<K,V>[]) EMPTY_TABLE;
    43 
    44     // Read in number of buckets
    45     s.readInt(); // ignored.
    46 
    47     // Read number of mappings
    48     int mappings = s.readInt();
    49     if (mappings < 0)
    50         throw new InvalidObjectException("Illegal mappings count: " +
    51                                            mappings);
    52 
    53     // capacity chosen by number of mappings and desired load (if >= 0.25)
    54     int capacity = (int) Math.min(
    55                 mappings * Math.min(1 / loadFactor, 4.0f),
    56                 // we have limits...
    57                 HashMap.MAXIMUM_CAPACITY);
    58 
    59     // allocate the bucket array;
    60     if (mappings > 0) {
    61         inflateTable(capacity);
    62     } else {
    63         threshold = capacity;
    64     }
    65 
    66     init();  // Give subclass a chance to do its thing.
    67 
    68     // Read the keys and values, and put the mappings in the HashMap
    69     for (int i = 0; i < mappings; i++) {
    70         K key = (K) s.readObject();
    71         V value = (V) s.readObject();
    72         putForCreate(key, value);
    73     }
    74 }

    十二、遍历

      HashMap 的遍历涉及了三个常用方法:获取的 value()值,获取所有的 key 值和获取所有的映射关系。

     1 //获取 HashMap 中所有的 value,可以重复无序,用 Collection 来放
     2 public Collection<V> values() {
     3     Collection<V> vs = values;
     4     return (vs != null ? vs : (values = new Values()));
     5 }
     6 //获取所有的 key,不可重复且无序,用 Set 来放
     7 public Set<K> keySet() {
     8     Set<K> ks = keySet;
     9     return (ks != null ? ks : (keySet = new KeySet()));
    10 }
    11 
    12 //返回 key-value 的映射关系,无序且不重复,用 Set 来放
    13 public Set<Map.Entry<K,V>> entrySet() {
    14     return entrySet0();
    15 }
    16 
    17 private Set<Map.Entry<K,V>> entrySet0() {
    18     Set<Map.Entry<K,V>> es = entrySet;
    19     return es != null ? es : (entrySet = new EntrySet());
    20 }

      更多的学习查看这一篇:HashMap实现底层细节之keySet,values,entrySet的一个底层实现细节  

    十三、其他方法

      下面为 HashMap 中其他的常用方法:

     1 //获取 HashMap中元素的个数
     2 public int size() {
     3     return size;
     4 }
     5 
     6 //判断 Map 是否为空(查看是否有元素)
     7 public boolean isEmpty() {
     8     return size == 0;
     9 }
    10 
    11 //判断 Map 是否包含指定的 key
    12 public boolean containsKey(Object key) {
    13     return getEntry(key) != null;
    14 }
    15 
    16 //清除Map中所有的元素
    17 public void clear() {
    18     modCount++;
    19     //使用 Arrays 工具类,把 table 里元素都置为 null 
    20     Arrays.fill(table, null);
    21     size = 0;
    22 }
    23 
    24 //判断是否包含指定的 value值
    25 public boolean containsValue(Object value) {
    26     if (value == null)
    27         return containsNullValue();
    28 
    29     Entry[] tab = table;
    30     for (int i = 0; i < tab.length ; i++)
    31         for (Entry e = tab[i] ; e != null ; e = e.next)
    32             if (value.equals(e.value))
    33                 return true;
    34     return false;
    35 }
    36 private boolean containsNullValue() {
    37     Entry[] tab = table;
    38     for (int i = 0; i < tab.length ; i++)
    39         for (Entry e = tab[i] ; e != null ; e = e.next)
    40             if (e.value == null)
    41                 return true;
    42     return false;
    43 }

    十四、

    十五、

    本篇文章基于 JDK7(jdk1.7.0_80)进行剖析学习。

  • 相关阅读:
    ISAPI实现静态页面后并用c#实现分页
    aspx里构造函数里无法使用session,需要重写一个方法放在load里面就能正常使用session了
    记录学习MVC过程,MVC异步请求(五)
    记录学习MVC过程,MVC验证(四)
    记录学习MVC过程,MVC简单路由(三)
    【读书笔记】【韭菜的自我修养】
    【中间件】redis的学习
    【java基础】线程池
    【算法】leetcode刷题 腾讯精选50题 一
    【碎语】让你废掉的七个行为
  • 原文地址:https://www.cnblogs.com/niujifei/p/14750614.html
Copyright © 2020-2023  润新知