一、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)进行剖析学习。