JDK1.8 中文在线版


超类分析

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

HashMap继承关系图

HashMap实现了Map、Cloneable、Serializable。

【接口层】java.util.Map<K,V>

Map UML

【抽象层】java.util.AbstractMap<K,V>

image.png

AbstractMap作为Map的骨架实现,基本实现了除put、entrySet之外的方法,主要减少实现Map的工作量。
而put、entrySet实现取决于Map的数据存储结构,AbstractMap将其交由子类实现。

[size, isEmpty, containsKey, containsValue, get, remove, clear]
上述方法基于entrySet视图实现,部分代码如下:

    public int size() {
        return entrySet().size();
    }
    ...
    // 基于entrySet的迭代器遍历查找V
    public V get(Object key) {
        Iterator<Entry<K,V>> i = entrySet().iterator();
            略... 考虑key为null的情况
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                if (key.equals(e.getKey()))
                    return e.getValue();
            }
        return null;
    }
    ...
    public void clear() {
        entrySet().clear();
    }

[putAll]
基于put方法实现

    public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }

[keySet, values]
基于entrySet的迭代器创建keySet、values视图(适配器模式?)

    // keySet视图
    transient Set<K>        keySet;
    // values视图
    transient Collection<V> values;
    
    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            // 基于entrySet迭代器构建AbstractSet匿名内部类作为keySet视图
            ks = new AbstractSet<K>() {
                public Iterator<K> iterator() {
                    return new Iterator<K>() {
                        private Iterator<Entry<K,V>> i = entrySet().iterator();
                        public boolean hasNext() { return i.hasNext(); }
                        public K next() { return i.next().getKey(); }
                        public void remove() { i.remove(); }
                    };
                }
                public int size() { return AbstractMap.this.size(); }
                public boolean isEmpty() { return AbstractMap.this.isEmpty(); }
                public void clear() { AbstractMap.this.clear(); }
                public boolean contains(Object k) { return AbstractMap.this.containsKey(k); }
            };
            keySet = ks;
        }
        return ks;
    }

    public Collection<V> values() {
        Collection<V> vals = values;
        if (vals == null) {
            // 同keySet, 考虑value的不唯一性,构建AbstractCollection匿名内部类
            vals = new AbstractCollection<V>() {
                ...
            };
            values = vals;
        }
        return vals;
    }

上述代码中keySet 和 values视图作为成员变量,存在线程安全问题。

本段介绍并分析了HashMap的超类,可能会有部分同学认为HashMap继承了AbstractMap,只需实现put、entrySet方法即可。
事实上HashMap重写了Map接口的所有方法,为什么HashMap需要重写AbstractMap已经实现的方法呢?
这个问题待各位同学看完全文之后再做思考。

HashMap源码分析

HashMap UML

上图仅列出了HashMap的成员变量及重要的实现方法(视图另外说明)。

本文将从以下几个方面对HashMap源码进行分析

  1. 散列算法
  2. 数据结构
  3. 初始化及扩容机制
  4. 视图
散列算法

HashMap的散列算法 (len - 1) & hash

// putVal中通过散列算法计算节点下标
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
        // tab=节点数组; p = 旧节点 不为空 => hash冲突
        // n=数组长度; i=数组下标(散列算法的结果值)
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        
        // n = tab.length 赋值n为节点数组的长度
        if ((tab = table) == null || (n = tab.length) == 0)
            ...
            
        // i = (n - 1) & hash 通过散列算法计算下标i, 并取该下标节点赋值给p    
        if ((p = tab[i = (n - 1) & hash]) == null)
            ...;
        else {
            ... 解决hash冲突
        }
       ... 新增/覆盖操作之后 扩容判断 + 新增回调 + 返回旧节点
    }

Ps: hash冲突 => hash相同;
HashMap不允许重复的key, 为什么hash会相同? => euqals相同,hash一定相同; hash相同, equals不一定相同;

如何减少hash冲突?
HashMap的散列算法表达式 (len - 1) & hash 中只存在两个变量。

len优化

如何优化len呢?
基于散列算法采用&位运算符, 当len为2的幂次方时hash冲突概率最低,分析如下:
设:hash1 = 11(1011); hash2 = 9(1001)

len为32时 len - 1 = 31(0001 1111)
hash1 & 31 = 11(1011); hash2 & 31 = 9(1001)
此时无hash冲突。

len为30时 len - 1 = 29(0001 1101)
hash1 & 29 = 9(1001); hash2 & 29 = 9(1001)
因为29的低2位为0,导致 hash1和hash2的低2位的数据丢失,散列值相同造成hash冲突。

    // 在指定初始容量(initialCapacity)的构建函数中对len进行了优化
    public HashMap(int initialCapacity, float loadFactor) {
        ... 前置校验
        this.loadFactor = loadFactor; // 加载因子
        
        // threshold => 容量扩容的临界值, 也可记录初始容量。
        // tableSizeFor(initialCapacity) 对传入的容初始量值做处理
        this.threshold = tableSizeFor(initialCapacity); 
    }

/** 将长度扩为2的幂次方
* 思路:
*      1 循环
*        int newCap = 1; 
*        while((newCap = newCap << 1) < cap);
*        return newCap;
*      2 二进制 
*        如 17的二进制为      (0001 0001)
*        最高有效位为第五位   (8765 4321)下标
*        将最高有效位(5)后    (0001 1111) => 31 + 1
*        都设为1即可,并+1即可
*        实现思路如下
*/ 
static final int tableSizeFor(int cap) {
        int n = cap - 1; // 假设 cap=17 n = 16(0001 0000)
        n |= n >>> 1; // n=>24(0001 1000) = 16(0001 0000) |= 8(1000)
        n |= n >>> 2; // n=>30(0001 1110) = 24(0001 1000) | = 6(0110)
        n |= n >>> 4; // n=>31(0001 1111) = 30(0001 1110) | = 1(0001)
        n |= n >>> 8; // n=>31(0001 1111) = 31(0001 1111) | = 0(0000)
        n |= n >>> 16; // n=>31(0001 1111) = 31(0001 1111) | = 0(0000)
        
        // return 32(31 + 1) 
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

hash优化

HashMap的默认容量为 1<<4(16),最大容量为 1<<30(1,073,741,824)

    // 默认初始容量
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    // 容量最大值
    static final int MAXIMUM_CAPACITY = 1 << 30;

HashMap的容量值通常不会太大,导致hash的高位不会参与散列算法。
所以HashMap通过将hash ^ hash的高16位,实现高16位参与散列算法,降低hash冲突的概率。

    static final int hash(Object key) {
        int h;
        // 当key非空时,取key的hash ^ key的hash >>> 16(即hash的高16位)
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
数据结构

有空再写


roylion
204 声望25 粉丝

读书破万卷