JDK源码阅读--HashMap
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
基于hash表的Map集合,允许key=null和value=null,HashMap是不同步的,不能保证Map集合的顺序,它是无序的Map集合.
HashMap有两个参数影响它的性能: 初始化容量(initial capacity) 和 负载因子(load factor)。
初始化容量:就是创建 hash表时的容量
负载因子:负载因子是衡量在哈希表的容量被自动增加之前,哈希表被允许获得多少满的度量。
当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表将被重新哈希(即重新构建内部数据结构),
这样哈希表的桶数大约是桶数的两倍。
默认的负载因子是0.75f,这是一个在时间和空间上的一个折中;较高的值减少了空间开销,但增加了查找成本(主要表现在HaspMap的get和put操作)。
如果初始容量大于最大条目数除以负载因子,则不会发生任何重哈希操作。
底层数据结构是链表数组,
1 /** 2 * 默认初始化容量是16 3 */ 4 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 5 6 /** 7 * 最大的容量,如果较高的值由带参数的任何构造函数隐式指定,则使用。 8 * 必须是2的幂,最大容量为1073741824 9 */ 10 static final int MAXIMUM_CAPACITY = 1 << 30;//1073741824 11 12 /** 13 * 当构造函数中没有指定时使用的负载因子,默认是0.75f; 14 */ 15 static final float DEFAULT_LOAD_FACTOR = 0.75f; 16 17 // 一个桶的树化阈值 18 // 当桶中元素个数超过这个值时,需要使用红黑树节点替换链表节点 19 // 这个值必须为 8,要不然频繁转换效率也不高 20 static final int TREEIFY_THRESHOLD = 8; 21 22 // 一个树的链表还原阈值 23 // 当扩容时,桶中元素个数小于这个值,就会把树形的桶元素 还原(切分)为链表结构 24 // 这个值应该比上面那个小,至少为 6,避免频繁转换 25 static final int UNTREEIFY_THRESHOLD = 6; 26 27 28 // 哈希表的最小树形化容量 29 // 当哈希表中的容量大于这个值时,表中的桶才能进行树形化 30 // 否则桶内元素太多时会扩容,而不是树形化 31 // 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD 32 static final int MIN_TREEIFY_CAPACITY = 64;
基本的hash表节点:
1 /** 2 * 基本的hash表节点, 3 * (参见下面的forTreeNode子类,以及LinkedHashMap中的条目子类。) 4 */ 5 static class Node<K,V> implements Map.Entry<K,V> { 6 final int hash;//hash值 final修饰的 7 final K key;//键 final修饰的 8 V value;//值 9 Node<K,V> next;//后置节点 10 11 //构造函数 12 Node(int hash, K key, V value, Node<K,V> next) { 13 this.hash = hash; 14 this.key = key; 15 this.value = value; 16 this.next = next; 17 } 18 19 public final K getKey() { return key; } 20 public final V getValue() { return value; } 21 public final String toString() { return key + "=" + value; } 22 23 public final int hashCode() { 24 return Objects.hashCode(key) ^ Objects.hashCode(value); 25 } 26 27 public final V setValue(V newValue) { 28 V oldValue = value; 29 value = newValue; 30 return oldValue; 31 } 32 33 public final boolean equals(Object o) { 34 if (o == this) 35 return true; 36 if (o instanceof Map.Entry) { 37 Map.Entry<?,?> e = (Map.Entry<?,?>)o; 38 if (Objects.equals(key, e.getKey()) && 39 Objects.equals(value, e.getValue())) 40 return true; 41 } 42 return false; 43 } 44 }
1 /** 2 * The table, initialized on first use, and resized as 3 * necessary. When allocated, length is always a power of two. 4 * (We also tolerate length zero in some operations to allow 5 * bootstrapping mechanics that are currently not needed.) 6 * 第一次初始化的时候使用这个表。当分配时,长度总是2的幂 7 */ 8 transient Node<K,V>[] table; 9 10 /** 11 * Holds cached entrySet(). Note that AbstractMap fields are used for keySet() and values(). 12 * 保存缓存entrySet()。注意AbstractMap字段用于keySet()和values()。 13 */ 14 transient Set<Map.Entry<K,V>> entrySet; 15 16 /** 17 * The number of key-value mappings contained in this map. 18 * 此映射中包含的键值映射的数目。 19 */ 20 transient int size; 21 22 /** 23 * 被修改的次数 24 */ 25 transient int modCount; 26 27 28 29 /** 30 * The next size value at which to resize (capacity * load factor). 31 * 要调整大小的下一个大小值(容量*负载因子)。 32 * @serial 33 */ 34 int threshold;//要调整大小的下一个大小值(容量*负载因子)。 35 36 /** 37 * 哈希表的负载因子。 38 * @serial 39 */ 40 final float loadFactor;
构造函数:
1 /** 2 * 构造一个空的HashMap,使用专门的初始化容量和默认的负载因子。 3 * @param initialCapacity the initial capacity. 4 * @throws IllegalArgumentException if the initial capacity is negative. 5 */ 6 public HashMap(int initialCapacity) { 7 this(initialCapacity, DEFAULT_LOAD_FACTOR); 8 } 9 10 /** 11 * (16) and the default load factor (0.75). 12 * 构造一个空的HashMap,使用默认的初始化容量(16)和默认的负载因子(0.75f)。 13 */ 14 public HashMap() { 15 this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted 16 } 17 18 /** 19 * 构造一个新的HashMap,使用与指定映射相同的映射。装载因子使用默认的(0.75f),和足够容纳指定Map中的映射的初始容量。 20 * @param m the map whose mappings are to be placed in this map 要在此映射中放置其映射的映射的映射 21 * @throws NullPointerException if the specified map is null 如果这个指定的map为null,则抛出空指针异常NullPointerException 22 */ 23 public HashMap(Map<? extends K, ? extends V> m) { 24 this.loadFactor = DEFAULT_LOAD_FACTOR; 25 putMapEntries(m, false); 26 }
1 /** 2 * 实现 Map.putAll和 Map的构造函数 3 * @param m the map 4 * @param evict 当最初构造这个map的时候为false,否则为true(传递到之后的插入节点的方法中)。 5 */ 6 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { 7 int s = m.size(); 8 if (s > 0) { 9 if (table == null) { // pre-size 10 float ft = ((float)s / loadFactor) + 1.0F; 11 int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); 12 if (t > threshold) { 13 threshold = tableSizeFor(t); 14 } 15 }else if (s > threshold) { 16 resize(); 17 } 18 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { 19 K key = e.getKey(); 20 V value = e.getValue(); 21 putVal(hash(key), key, value, false, evict); 22 } 23 } 24 }
1 /** 2 * @return 返回此映射中键值映射的数目。 3 */ 4 public int size() { 5 return size; 6 } 7 8 /** 9 * 判断该map是否为空 10 * @return 当此map中不含键值对的时候,返回true. 11 */ 12 public boolean isEmpty() { 13 return size == 0; 14 }
get方法(先比较hash,若相等在比较equals):
1. Key==null的时候,判断map也为空,就返回null
2. 根据node数组下标找到node数组下标
3. 如果当前node链表只存在一个数据就直接取value值
如果当前node链表存在多个node元素,则循环遍历node链表,分别对他们的hash值和key值,value值进行判断,知道找到node以后返回Value值,如果没有找到返回null。
1 /** 2 * 根据键key获取值value,如果此map不包含这个key,则返回null。 3 * Returns the value to which the specified key is mapped, 4 * or {@code null} if this map contains no mapping for the key. 5 * 6 * <p>More formally, if this map contains a mapping from a key 7 * {@code k} to a value {@code v} such that {@code (key==null ? k==null : 8 * key.equals(k))}, then this method returns {@code v}; otherwise 9 * it returns {@code null}. (There can be at most one such mapping.) 10 * 11 * <p>A return value of {@code null} does not <i>necessarily</i> 12 * indicate that the map contains no mapping for the key; it's also 13 * possible that the map explicitly maps the key to {@code null}. 14 * The {@link #containsKey containsKey} operation may be used to 15 * distinguish these two cases. 16 * 17 * @see #put(Object, Object) 18 */ 19 public V get(Object key) { 20 Node<K,V> e; 21 return (e = getNode(hash(key), key)) == null ? null : e.value; 22 } 23 24 /** 25 * Implements Map.get and related methods 26 * Map.get相关方法的实现 27 * @param hash hash for key 键key的hash值 28 * @param key the key 键key 29 * @return the node, or null if none 返回根据key及key的hash找到的这个节点;如果这个节点为空,则返回null 30 * 31 * 32 */ 33 final Node<K,V> getNode(int hash, Object key) { 34 Node<K,V>[] tab; 35 Node<K,V> first, e; 36 int n; 37 K k; 38 if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { 39 // always check first node 总是会检查哈希表的第一个节点 40 if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) { 41 return first; 42 } 43 if ((e = first.next) != null) {//如果第一个节点的下一个节点不为空 44 if (first instanceof TreeNode) {//如果该节点是TreeNode类型的(红黑树) 45 return ((TreeNode<K, V>) first).getTreeNode(hash, key);//调用红黑树的查找方法 46 } 47 do { 48 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { 49 return e; 50 } 51 } while ((e = e.next) != null); 52 } 53 } 54 return null; 55 }
put方法:
1. 首先判断node数组,也就是数组是否为空,为空的话就初始化hashmap
2. 如果node数组不为空的话,就判断key是否为空,为空的话就将放到数组的第一个位置
3. 就通过key获取hash值,通过hash值做比较,如果key的hash值相等 并且key.equals(e.key)也相等的话,就将新的value替换掉旧的。如果条件不满足就创建一个node,且用上一个node的next指向新创建的node 。
1 /** 2 * 往map中添加新的键值对,如果键key存在,则将该键key对应的旧值value替换为新值value 3 * 4 * @param key 要与指定值关联的键 5 * @param value 值与指定的键关联 6 * @return 与key关联的前一个值,如果没有key的映射,则为null。(null返回值还可以指示以前将null与key关联的映射。) 7 */ 8 public V put(K key, V value) { 9 return putVal(hash(key), key, value, false, true); 10 } 11 12 /** 13 * Implements Map.put and related methods 14 * 实现Map.put和相关方法 15 * @param hash hash for key key的hash值 16 * @param key the key 键 17 * @param value the value to put 值 18 * @param onlyIfAbsent 如果是true,不能改变已存在的值 19 * @param evict 如果为false,该表处于创建模式 20 * @return 返回前一个值,如果没有,则为空 21 * 根据key算hash,根据容量和hash算index,table[index]没有直接添加到数组中,table[index]有,若index位置同一个key则更新, 22 * 否则遍历next是否有,有则更新,无则新增,最后根据thread与size判断是否扩容。注:扩容时容量翻倍,重新算hash复制到新数组 23 * 24 */ 25 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 26 boolean evict) { 27 Node<K,V>[] tab; 28 Node<K,V> p; 29 int n, i; 30 if ((tab = table) == null || (n = tab.length) == 0) { 31 n = (tab = resize()).length; 32 } 33 if ((p = tab[i = (n - 1) & hash]) == null) { 34 tab[i] = newNode(hash, key, value, null); 35 }else { 36 Node<K,V> e; K k; 37 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) { 38 e = p; 39 }else if (p instanceof TreeNode) { 40 e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value); 41 } 42 else { 43 for (int binCount = 0; ; ++binCount) { 44 if ((e = p.next) == null) { 45 p.next = newNode(hash, key, value, null); 46 if (binCount >= TREEIFY_THRESHOLD - 1) { // -1 for 1st 47 treeifyBin(tab, hash); 48 } 49 break; 50 } 51 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { 52 break; 53 } 54 p = e; 55 } 56 } 57 if (e != null) { // existing mapping for key 58 V oldValue = e.value; 59 if (!onlyIfAbsent || oldValue == null) { 60 e.value = value; 61 } 62 afterNodeAccess(e); 63 return oldValue; 64 } 65 } 66 ++modCount; 67 if (++size > threshold) { 68 resize(); 69 } 70 afterNodeInsertion(evict); 71 return null; 72 }
不能只满足于写完代码运行结果正确就完事,时常考虑如何让代码更加简练更加容易维护、容易扩展和复用,只有这样才可以真正得到提高
--《来自大话设计模式》