HashMap 可以说是我们在开发中经常使用的到, 在 Java 的数据结构基础里,HashMap 无疑是一个非常重要的数据结构 现在看看源码里面的方法具体都干了一下什么

简单使用

1
2
3
4
5
6
7
 HashMap<String, String> map = new HashMap<>();
	        map.put(null, null);
	        map.put("1", "22");
	        map.put("2", "33");
	        map.put("1", "22");
	        map.get("2");
	        map.remove("2");

首先看看源码中都有那些成员变量, 然后我们根据上面的数据存储方式,看看源码中经历了什么

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//默认的初始容量为16,必须是2的幂次方。    
static final int DEFAULT_INITIAL_CAPACITY =1 << 4; //  16;    
//最大容量,默认为2的30次方,    
static final int MAXIMUM_CAPACITY = 1 << 30;    
//加载因子    
static final float DEFAULT_LOAD_FACTOR = 0.75f;    
// 一个空的数组   
static final HashMapEntry<?,?>[] EMPTY_TABLE = {};    
//数组表,大小可以改变,且大小必须为2的幂    
transient HashMapEntry<K,V>[] table = (HashMapEntry<K,V>[]) EMPTY_TABLE;    
//当前实时的Map中key-value映射的个数    
transient int size;    
//当实际大小超过临界值时,会进行扩容threshold = 加载因子*容量  ==》 16*0.75=12  
int threshold;    
//加载因子  0.75 
final float loadFactor = DEFAULT_LOAD_FACTOR;    
//Hash表结构性修改次数    
transient int modCount;

无参数构造方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  public HashMap() {
        // 当我们直接 new 对象创建的时候, 根据我们上面的成员变量可以知道, 创建了一个容量为16,加载因子为0.75
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    
    // 然后是直接到了这个地方 
     public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
                                               
         // 这里做了一个判断, 比最大值还大的时候, 默认最大值的容量大小                                      
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        // 成员变量的赋值操作
        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();  // 这个默认是一个空的实现, 到这里, 我们就算是结束了
    }

在上面的注释中发现加载因子, 何为加载因子, 我们都知道 HashMap 是一个动态扩容的散列列表, 那么他是怎么触发扩容的呢, 他是 (当前的实时容量 > 集合容量*加载因子 ) 比如我们默认第一次初始化的容量是16, 那么16 * 0.75 = 12, 一般这个值的作用是用来判断是否需要扩容的, 其实是否需要扩容还有另外一个触发条件,后面会分析到

有参构造

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
 public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    
    
    //这里还是一样的, 只不过自己指定了大小
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

存储数据put方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 public V put(K key, V value) {
        // 一开始table为空的时候,创建一个数组,初始化容量大小和加载因子threshold
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        // 这句话说明可以存储一个为null的key的元素
        if (key == null)
            return putForNullKey(value);
        // 根据key计算出hash值
        int hash = hash(key);
        // 根据hash值和表的长度, 计算元素需要存储的位置, (后续也可以直接根据key,其实换算后的index值,就可以快速确定元素在表中的位置, 像数组直接查询index的位置一样,速度快)
        int i = indexFor(hash, table.length);
        
        // 判断是否存在key相同的元素,有则会覆盖
        for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //如果key相同的话, 就直接覆盖value值,更新作用, 也就是说, key相同的话,会覆盖旧的元素
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);  // 更新了数据都会回调这个方法
                 //如果相同key,则返回修改了的老元素,结束方法
                return oldValue;
            }
        }

        // 计数器增加1(如果是修改老元素的话,不会计数的)
        modCount++;
        //添加新元素, key不相同, 根据hash值,key,value, 表中的位置,将元素存储起来
        addEntry(hash, key, value, i);
        return null;
    }

创建表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/**
     * Inflates the table. 
     */
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize // 找一个2的倍数
        int capacity = roundUpToPowerOf2(toSize);

        // 按照上面的描述, 首次是16大小, 16 * 0.75 = 12,threshold为12 
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity]; // 创建了Evtry对象, 下面看看这个对象的结构
        initHashSeedAsNeeded(capacity);
    }

数据结构的方式, Entry对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key; // 存储的kay
        V value;
        Entry<K,V> next; // 链向下一个 对象
        int hash;   // 转换的hash值

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        
        // 重写了equals方法,对元素进行比较
        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }

        /**
         * This method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that's already
         * in the HashMap.
         */
        void recordAccess(HashMap<K,V> m) {
        }

        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void recordRemoval(HashMap<K,V> m) {
        }
    }

存储可以为null的key键

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private V putForNullKey(V value) {
        // 查找key为null的位置, 修改老元素,并返回
        for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
            //  这里和上面的操作是一样的, 如果是key相同的话,也做到更新,覆盖的操作
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                // 返回老元素
                return oldValue;
            }
        }
        modCount++;
        // 增加元素key为null的元素
        addEntry(0, null, value, 0);
        return null;
    }

添加新的元素 addEntry()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    void addEntry(int hash, K key, V value, int bucketIndex) {
        // 这里就是触发扩容的条件了
        // 如果size大于临界值的话,将进行扩容
        // 有两个条件: 1当前元素存储的个数大于阀值 + 是否发生了hash冲突
        if ((size >= threshold) && (null != table[bucketIndex])) {
            // 进行扩容,原来的2倍
            resize(2 * table.length);
            // 下面是扩容后,重新计算value需要存储的位置
            // 如果key为null的话,那么hash值为0
            hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
            // 根据hash值重新 计算在表中的位置
            bucketIndex = indexFor(hash, table.length);
        }

        // 数据插入
        createEntry(hash, key, value, bucketIndex);
    }

createEntry 创建新的对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void createEntry(int hash, K key, V value, int bucketIndex) {
    //情况1: 当没有发生hash冲突的时候 table[bucketIndex] 为null,  e == null ,然后创建一个新的HashMapEntry<>(hash, key, value, null); 给 	table[bucketIndex]
    
    
    // 情况2: 当发送了hash冲突的时候, 那么table[bucketIndex] 和 e 都不为空了, 
    // 那么就形成了一个单链表 table[bucketIndex] = new HashMapEntry<>(hash, key, value(新的值), 原来的值 e);
    
	HashMapEntry<K,V> e = table[bucketIndex];         
	table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
	size++;  //计数
	}
}

get方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    public V get(Object key) {
        if (key == null) //获取key 为null的value值
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
    
    
    // getEntry方法
    final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        // 根据key计算出hash值
        int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
        
        // 使用hash值,计算元素在表中的位置,找到后,将改元素返回
        for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
             
             //查找key相同的返回
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

删除元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    
     public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        // 删除成功, 将删除的 value 返回
        return (e == null ? null : e.value);
    }

    // removeEntryForKey方法
    final Entry<K,V> removeEntryForKey(Object key) {
        if (size == 0) {
            return null;
        }
        int hash = (key == null) ? 0 : hash(key);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null) {
           
            Entry<K,V> next = e.next;
            Object k;
             // 遍历循环,查找key相同的,删除,
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++; // 修改的次数
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }

总结

好了, 到这里差不多分析完了, 总结一下, 创建过程, 添加数据,删除数据,都做了那些事情

底层的数据结构是其实是数组(数组+链表) (链表散列,即是数组和链表的结合体)

创建一个对象,默认初始化都容量大小为 16, 除非是自己指定了大小, 然后初始化加载因子为 0.75, 然后添加数据, 创建表, 创建 threshold, 然后对为 null 的key, 进行判断存储, 如果不为 null 的值, 对 key 进行 hash, 计算将要存储在表中的位置, 然后 for 循环遍历查找是否存在这个 key 的 value, 存在则更新, 然后将其返回, 否则添加数据, 然后判断是否扩容(当前的实时的容量大小是否 >= threshold + 是否发生了 hash 冲突) , 符合条件触发为 2 的倍数扩容, 然后添加元素

获取元素: 通过 key 计算 hash 值, 然后根据hash值计算在表中的位置, 通过比较 key 是否相同, 相同则返回

删除元素: 通过 key 计算 hash 值, 然后计算在表中的位置, 通过比较 key 是否相同, 相同将其删除, 然后返回