HashMap源码理解与分析

Posted huster-田浪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap源码理解与分析相关的知识,希望对你有一定的参考价值。

/**
  * HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现。与HashTable主要区别为不支持同步和允许null作为key和value。
  * HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。
  * 如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
  * 在JDK1.6中,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。
  * 但是当位于一个数组中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。
  * 而JDK1.8中,HashMap采用数组+链表+红黑树实现,当链表长度超过阈值8时,将链表转换为红黑树,这样大大减少了查找时间。
  * 原本Map.Entry接口的实现类Entry改名为了Node。转化为红黑树时改用另一种实现TreeNode。
  */
 1 public class HashMap<K, V> extends AbstractMap<K, V>
 2         implements Map<K, V>, Cloneable, Serializable {
 3 
 4     private static final long serialVersionUID = 362498820763181265L;
 5 
 6 
 7     /**
 8      * 默认的初始容量(容量为HashMap中槽的数目)是16,且实际容量必须是2的整数次幂。
 9      */
10     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
11 
12     /**
13      * 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
14      */
15     static final int MAXIMUM_CAPACITY = 1 << 30;
16 
17     /**
18      * 默认装填因子0.75,如果当前键值对个数 >= HashMap最大容量*装填因子,进行rehash操作
19      */
20     static final float DEFAULT_LOAD_FACTOR = 0.75f;
21 /**
22      * JDK1.8 新加,Entry链表最大长度,当桶中节点数目大于该长度时,将链表转成红黑树存储;
23      */
24     static final int TREEIFY_THRESHOLD = 8;
25 
26     /**
27      * JDK1.8 新加,当桶中节点数小于该长度,将红黑树转为链表存储;
28      */
29     static final int UNTREEIFY_THRESHOLD = 6;
30 
31     /**
32      * 桶可能被转化为树形结构的最小容量。当哈希表的大小超过这个阈值,才会把链式结构转化成树型结构,否则仅采取扩容来尝试减少冲突。
33      * 应该至少4*TREEIFY_THRESHOLD来避免扩容和树形结构化之间的冲突。
34      */
35     static final int MIN_TREEIFY_CAPACITY = 64;
36 
37  /**
38      * JDK1.6用Entry描述键值对,JDK1.8中用Node代替Entry
39      */
40     static class Node<K, V> implements Map.Entry<K, V> {
41         // hash存储key的hashCode
42         final int hash;
43         // final:一个键值对的key不可改变
44         final K key;
45         V value;
46         //指向下个节点的引用
47         Node<K, V> next;
48 
49         //构造函数
50         Node(int hash, K key, V value, Node<K, V> next) {
51             this.hash = hash;
52             this.key = key;
53             this.value = value;
54             this.next = next;
55         }
56 
57         public final K getKey() {
58             return key;
59         }
60 
61         public final V getValue() {
62             return value;
63         }
64 
65         public final String toString() {
66             return key + "=" + value;
67         }
68 
69         public final int hashCode() {
70             return Objects.hashCode(key) ^ Objects.hashCode(value);
71         }
72 
73         public final V setValue(V newValue) {
74             V oldValue = value;
75             value = newValue;
76             return oldValue;
77         }
78     public final boolean equals(Object o) {
79             if (o == this)
80                 return true;
81             if (o instanceof Map.Entry) {
82                 Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
83                 if (Objects.equals(key, e.getKey()) &&
84                         Objects.equals(value, e.getValue()))
85                     return true;
86             }
87             return false;
88         }
89     }
/**
  * HashMap中键值对的存储形式为链表节点,hashCode相同的节点(位于同一个桶)用链表组织
  * hash方法分为三步:
  * 1.取key的hashCode
  * 2.key的hashCode高16位异或低16位
  * 3.将第一步和第二步得到的结果进行取模运算。
  */
 1 static final int hash(Object key) {
 2         int h;
 3         //计算key的hashCode, h = Objects.hashCode(key)
 4         //h >>> 16表示对h无符号右移16位,高位补0,然后h与h >>> 16按位异或
 5         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 6     }
 7 
 8     /**
 9      * 如果参数x实现了Comparable接口,返回参数x的类名,否则返回null
10      */
11     static Class<?> comparableClassFor(Object x) {
12         if (x instanceof Comparable) {
13             Class<?> c;
14             Type[] ts, as;
15             Type t;
16             ParameterizedType p;
17             if ((c = x.getClass()) == String.class) // bypass checks
18                 return c;
19             if ((ts = c.getGenericInterfaces()) != null) {
20                 for (int i = 0; i < ts.length; ++i) {
21                     if (((t = ts[i]) instanceof ParameterizedType) &&
22                             ((p = (ParameterizedType) t).getRawType() ==
23                                     Comparable.class) &&
24                             (as = p.getActualTypeArguments()) != null &&
25                             as.length == 1 && as[0] == c) // type arg is c
26                         return c;
27                 }
28             }
29         }
30         return null;
31     }
32 
33     /**
34      * 如果x的类型为kc,则返回k.compareTo(x),否则返回0
35      */
36     @SuppressWarnings({"rawtypes", "unchecked"}) // for cast to Comparable
37     static int compareComparables(Class<?> kc, Object k, Object x) {
38         return (x == null || x.getClass() != kc ? 0 :
39                 ((Comparable) k).compareTo(x));
40     }
41 
42     /**
43      * 结果为>=cap的最小2的自然数幂
44      */
45     static final int tableSizeFor(int cap) {
46         //先移位再或运算,最终保证返回值是2的整数幂
47         int n = cap - 1;
48         n |= n >>> 1;
49         n |= n >>> 2;
50         n |= n >>> 4;
51         n |= n >>> 8;
52         n |= n >>> 16;
53         return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
54     }

 

/* ---------------- Fields -------------- */
 1  /**
 2      * 哈希桶数组,分配的时候,table的长度总是2的幂
 3      */
 4     transient Node<K, V>[] table;
 5 
 6     /**
 7      * HashMap将数据转换成set的另一种存储形式,这个变量主要用于迭代功能
 8      */
 9     transient Set<Map.Entry<K, V>> entrySet;
10 
11     /**
12      * 实际存储的数量,则HashMap的size()方法,实际返回的就是这个值,isEmpty()也是判断该值是否为0
13      */
14     transient int size;
15 
16     /**
17      * hashmap结构被改变的次数,fail-fast机制
18      */
19     transient int modCount;
20 
21     /**
22      * HashMap的扩容阈值,在HashMap中存储的Node键值对超过这个数量时,自动扩容容量为原来的二倍
23      *
24      * @serial
25      */
26     int threshold;
27 
28     /**
29      * HashMap的负加载因子,可计算出当前table长度下的扩容阈值:threshold = loadFactor * table.length
30      *
31      * @serial
32      */
33     final float loadFactor;

具体源码参考地址:

https://github.com/wupeixuan/JDKSourceCode1.8/blob/master/src/java/util/HashMap.java

以上是关于HashMap源码理解与分析的主要内容,如果未能解决你的问题,请参考以下文章

[JavaSE 源码分析] 关于HashMap的个人理解

HashMap源码分析

HashMap源码分析和面试准备

HashMap源码分析和面试准备

BAT大厂面试官必问的HashMap相关面试题及部分源码分析

彻底理解HashMap的元素插入原理