HashMap详解

466 阅读15分钟

散列表

散列表是一种以常数平均时间执行插入、删除和查找的数据结构。在对散列表中的元素进行操作的时候,通常只针对元素的某部分数据,这部分被称为关键字(key);我们把表的大小定义为tableSize,每个关键字被映射到[0, tableSize - 1]这个范围中的某个数,并且被放到数组中,这个映射被称为散列函数,理想情况下它应该计算简单并且保证任何两个不同的关键字映射到不同的位置;如果两个不同的关键字映射到相同的位置,这种情况被称为散列冲突;解决冲突常用的办法有分离链接法,它将映射到相同位置的元素用链表的形式保存。

基本属性

基本结构

HashMap是散列表的一种实现,它采用分离链接法来解决冲突并进行了一些优化。在HashMap里面,散列表是一个Node数组:

transient Node<K,V>[] table;

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

Node里面保存了keyvalue,关键字对应的hash值,hash值只会被计算一次,next指向下一个节点,形成链表以解决冲突。在HashMap里面还有另外一种节点:

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }
    
    ...省略部分代码
}

TreeNode是红黑树节点(红黑树的实现请移步这里),这就是从JDK8以后HashMap的优化,当链表长度到达一定长度后,链表会被重构成一颗红黑树。

static final int TREEIFY_THRESHOLD = 8;

static final int UNTREEIFY_THRESHOLD = 6;

static final int MIN_TREEIFY_CAPACITY = 64;

当散列表长度至少为64且链表节点数超过8才会将链表转化成红黑树;当红黑数节点小于6时,红黑树会被转化成链表。所以HashMap的基本结构如下所示:

散列函数

HashMap,关键字的定位分为两步,第一步计算关键字的hash值。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

散列函数将键的hashCode的高16位与低16位做异或操作,方法上的注释解释了为什么这样操作:

Computes key.hashCode() and spreads (XORs) higher bits of hash to lower. Because the table uses power-of-two masking, sets of hashes that vary only in bits above the current mask will always collide. (Among known examples are sets of Float keys holding consecutive whole numbers in small tables.) So we apply a transform that spreads the impact of higher bits downward. There is a tradeoff between speed, utility, and quality of bit-spreading. Because many common sets of hashes are already reasonably distributed (so don't benefit from spreading), and because we use trees to handle large sets of collisions in bins, we just XOR some shifted bits in the cheapest possible way to reduce systematic lossage, as well as to incorporate impact of the highest bits that would otherwise never be used in index calculations because of table bounds.

在确定键在数组中的位置(下标)的时候,因为数组的长度总是2的幂,所以它利用数组长度减一然后与键的哈希值做与操作替代了求余:

(n - 1) & hash

但当数组长度比较小的时候,参与计算的只有低位的值,所以为了避免高位没法参与位置的确定从而引起的碰撞,并综合速度、作用、质量,为了减少系统开销,于是采用了高位和低位异或的方式。假设数组长度为8,我们来计算以下hello world字符串的位置:

h = hashCode()        : 0110101011101111 1110001011000100 = 1794106052
h >>> 16              : 0000000000000000 0110101011101111
hash = h ^ (h >>> 16) : 0110101011101111 1000100000101011
8 - 1                 : 0000000000000000 0000000000000111
(n - 1) & hash        : 0000000000000000 0000000000000011 = 3

数组长度总是2的幂是通过以下方法实现的:

/**
 * HashMap方法
 * -1 = 0b11111111111111111111111111111111
 * 所以-1无符号右移n位,那么n = Math.pow(2, 32 - n) - 1
 * 如果n >= 32,n < 0,返回1 = Math.pow(2, 0)
 * 所以,无论怎样,数组的长度都是2的幂
 */
static final int tableSizeFor(int cap) {
    int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

/**
 * Integer里面的方法,返回i最高非0位前面0的个数
 * 比如 0000000000000000 0000000000000001 就返回 31
 * 比如 0000000000000000 1111111111111111 就返回 16
 * 比如 1000000000000000 0000000000000000 就返回 0
 * 计算方式类似2分法
 */
public static int numberOfLeadingZeros(int i) {
    // 0 = 0b00000000000000000000000000000000 返回32
    // i < 0,符号位一定是1,返回0
    if (i <= 0)
        return i == 0 ? 32 : 0;
    int n = 31;
    // 如果i >= 2的16次方,0的个数要减去16
    if (i >= 1 << 16) { n -= 16; i >>>= 16; }
    // 如果i >= 2的8次方,0的个数要减去8
    if (i >= 1 <<  8) { n -=  8; i >>>=  8; }
    // 如果i >= 2的4次方,0的个数要减去4
    if (i >= 1 <<  4) { n -=  4; i >>>=  4; }
    // 如果i >= 2的2次方,0的个数要减去2
    if (i >= 1 <<  2) { n -=  2; i >>>=  2; }
    // 如果i >= 2的1次方,0的个数减去1
    return n - (i >>> 1);
}

其他属性

HashMap中还有一些其他比较重要的属性:

// 默认初始数组大小 = 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

// 数组最大为2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;

// 默认装载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;

// 实际存储的元素个数
transient int size;

// 被修改次数
transient int modCount;

// 下一次扩容的阈值,当size = threshold时,扩容
// threshold = table.length * loadFactor
int threshold;

// 装载因子
final float loadFactor;

HashMap提供了四种构造方法:

 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;
    this.threshold = tableSizeFor(initialCapacity);
}

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}

源码解析

添加元素

/**
 * 插入元素,如果键已存在,用新值替换旧的
 */
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

/**
 * 插入元素,如果键已存在,不替换旧值
 */
@Override
public V putIfAbsent(K key, V value) {
    return putVal(hash(key), key, value, true, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 如果table为空或者长度为0,就先调整大小
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 如果指定位置为null,将新的节点放在指定位置就好了
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        // e是要插入的节点前继节点
        Node<K,V> e; K k;
        // 待插入的键已存在
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // 待插入的位置存储的树节点
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 遍历链表
            for (int binCount = 0; ; ++binCount) {
                // 找到末尾节点,将新节点插入
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 如果链表长度大于等于7(加上新插入的节点,就是8),将链表转化成树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 如果e不等于null,表示key已经存在
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            // 用于LinkedHashMap
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 如果元素个数达到阈值,resize
    if (++size > threshold)
        resize();
    // 用于LinkedHashMap
    afterNodeInsertion(evict);
    return null;
}

接下来看一下resize的实现,resize的方法注释如下:

Initializes or doubles table size. If null, allocates in accord with initial capacity target held in field threshold. Otherwise, because we are using power-of-two expansion, the elements from each bin must either stay at same index, or move with a power of two offset in the new table.

意思是:如果表为空,则初始化表(16),否则将表的长度扩充为原来的两倍。并且是使用2的幂进行扩展(新数组长度是旧数组长度的两倍),所以元素的位置要么不变,要么旧位置加上旧数组的长度。这是为啥呢?我们还是以hello world为例,数组从8扩展到16,原来它的位置是3:

扩容前:
hash = h ^ (h >>> 16) : 0110101011101111 1000100000101011
8 - 1                 : 0000000000000000 0000000000000111
(n - 1) & hash        : 0000000000000000 0000000000000011 = 3

扩容后:
hash = h ^ (h >>> 16) : 0110101011101111 1000100000101011
16 - 1                : 0000000000000000 0000000000001111
(n - 1) & hash        : 0000000000000000 0000000000001011 = 3 + 8 = 11

假设另外一个键的hash值是  0110101011101111 1000100000100011
扩容前:
hash = h ^ (h >>> 16) : 0110101011101111 1000100000100011
8 - 1                 : 0000000000000000 0000000000000111
(n - 1) & hash        : 0000000000000000 0000000000000011 = 3

扩容后:
hash = h ^ (h >>> 16) : 0110101011101111 1000100000100011
16 - 1                : 0000000000000000 0000000000001111
(n - 1) & hash        : 0000000000000000 0000000000000011 = 3

因此,在扩容的时候判断键的hash值新增的那一位的值就能确定它在新数组的位置,判断新增的那一位的值是这样判断的:

(e.hash & oldCap) == 0 在原位置,否则当前位置加上旧数组长度

hash = h ^ (h >>> 16) : 0110101011101111 1000100000101011
oldCap = 8            : 0000000000000000 0000000000001000
oldCap & hash         : 0000000000000000 0000000000001000 = 1

接下来看一下resize的源码:

final Node<K,V>[] resize() {
    // 这一部分确定新数组的大小,如果超过2的30次方,就不再扩容了
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {	
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    // 更新下一次resize的阈值
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        // 遍历节点,重新定位元素的位置,要么在原位置,要么原位置加上旧数组的长度
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                // 表明只有一个元素
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                // 如果是树节点,分裂树
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                // 遍历链表,并采用尾插法
                else { // preserve order
                    // 记录原位置的节点
                    Node<K,V> loHead = null, loTail = null;
                    // 记录原位置 + 原容量位置的节点
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        // 原位置
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        // 新位置
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    // 给指定位置赋值
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

/**
 * TreeNode.split
 * 树节点的分裂和之前链表的很类似,TreeNode是继承的Node,
 * 所以TreeNode也能通过next遍历
 */
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
    TreeNode<K,V> b = this;
    // Relink into lo and hi lists, preserving order
    TreeNode<K,V> loHead = null, loTail = null;
    TreeNode<K,V> hiHead = null, hiTail = null;
    int lc = 0, hc = 0;
    for (TreeNode<K,V> e = b, next; e != null; e = next) {
        next = (TreeNode<K,V>)e.next;
        e.next = null;
        if ((e.hash & bit) == 0) {
            if ((e.prev = loTail) == null)
                loHead = e;
            else
                loTail.next = e;
            loTail = e;
            ++lc;
        }
        else {
            if ((e.prev = hiTail) == null)
                hiHead = e;
            else
                hiTail.next = e;
            hiTail = e;
            ++hc;
        }
    }

    if (loHead != null) {
        // 如果节点数小于等于6,从树转成链表
        if (lc <= UNTREEIFY_THRESHOLD)
            tab[index] = loHead.untreeify(map);
        else {
            tab[index] = loHead;
            if (hiHead != null) // (else is already treeified)
                loHead.treeify(tab);
        }
    }
    if (hiHead != null) {
        if (hc <= UNTREEIFY_THRESHOLD)
            tab[index + bit] = hiHead.untreeify(map);
        else {
            tab[index + bit] = hiHead;
            if (loHead != null)
                hiHead.treeify(tab);
        }
    }
}

在链表长度达到阈值的时候,链表会转化成树:

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    // 如果表为空,或者表的长度小于64,先resize
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        // 先将节点转化成树节点
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
               tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            // 树化
            hd.treeify(tab);
    }
}

查找元素

/** 
 * 根据键查找值
 */
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    // table不为空,长度不为0,数组指定位置不为空
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        // 检查首节点,hash值相等且key相等,直接返回
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        // 循环查找
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

/**
 * 如果对于键不存在,返回默认的值
 */
@Override
public V getOrDefault(Object key, V defaultValue) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}

删除元素

public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    // table不为空,长度大于0,映射位置节点不为空
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, e; K k; V v;
        // 判断首节点是否是要删除的节点
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            // 如果是树节点,查找树
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            // 循环查找
            else {
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        // 找到的节点不为空且符合其他条件
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            // 删除树节点流程
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            // 普通链表操作
            else if (node == p)
                tab[index] = node.next;
            else
                p.next = node.next;
            ++modCount;
            --size;
            // 用于LinkedHashMap
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

/**
 * TreeNode.removeTreeNode
 */
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                          boolean movable) {
    int n;
    if (tab == null || (n = tab.length) == 0)
        return;
    int index = (n - 1) & hash;
    TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
    TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
    if (pred == null)
        tab[index] = first = succ;
    else
        pred.next = succ;
    if (succ != null)
        succ.prev = pred;
    if (first == null)
        return;
    if (root.parent != null)
        root = root.root();
    // 只有当root为空或者root的子节点为空的时候(元素个数在[0,2]之间)
    // 才会执行树转化成链表的操作
    if (root == null
        || (movable
            && (root.right == null
                || (rl = root.left) == null
                || rl.left == null))) {
        tab[index] = first.untreeify(map);  // too small
        return;
    }
    ...省略后续代码
}

迭代器

HashMap中,所有的迭代器,包括KeySetValuesEntrySet对应的迭代器都继承自HashIterator

abstract class HashIterator {
    Node<K,V> next;        // 下一个节点
    Node<K,V> current;     // 当前节点
    int expectedModCount;  // 防止迭代的时候元素被更改,达到快速失败的效果
    int index;             // 当前数组下标

    HashIterator() {
        expectedModCount = modCount;
        Node<K,V>[] t = table;
        current = next = null;
        index = 0;
        // 初始化的时候找到数组中第一个不为空的元素
        if (t != null && size > 0) { // advance to first entry
            do {} while (index < t.length && (next = t[index++]) == null);
        }
    }

    public final boolean hasNext() {
        return next != null;
    }

    final Node<K,V> nextNode() {
        Node<K,V>[] t;
        Node<K,V> e = next;
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        if (e == null)
            throw new NoSuchElementException();
        // 先遍历完链表,如果链表遍历完,找到数组中下一个不为空的元素
        if ((next = (current = e).next) == null && (t = table) != null) {
            do {} while (index < t.length && (next = t[index++]) == null);
        }
        return e;
    }

    public final void remove() {
        Node<K,V> p = current;
        if (p == null)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        current = null;
        removeNode(p.hash, p.key, null, false, false);
        expectedModCount = modCount;
    }
}

final class KeyIterator extends HashIterator
    implements Iterator<K> {
    public final K next() { return nextNode().key; }
}

final class ValueIterator extends HashIterator
    implements Iterator<V> {
    public final V next() { return nextNode().value; }
}

final class EntryIterator extends HashIterator
    implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { return nextNode(); }
}

所以,无论采用哪种方法(keySet()values()entrySet())遍历HashMap中的元素,顺序都是一样的。

序列化

HashMap的属性中,除了thresholdloadFactor,其他属性都加了transient关键字。

private void writeObject(java.io.ObjectOutputStream s)
    throws IOException {
    int buckets = capacity();
    // Write out the threshold, loadfactor, and any hidden stuff
    s.defaultWriteObject();
    s.writeInt(buckets);
    s.writeInt(size);
    // 写入存储的内容
    internalWriteEntries(s);
}
    
void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
    Node<K,V>[] tab;
    if (size > 0 && (tab = table) != null) {
        for (Node<K,V> e : tab) {
            for (; e != null; e = e.next) {
                s.writeObject(e.key);
                s.writeObject(e.value);
            }
        }
    }
}
    
private void readObject(java.io.ObjectInputStream s)
    throws IOException, ClassNotFoundException {
    // Read in the threshold (ignored), loadfactor, and any hidden stuff
    s.defaultReadObject();
    reinitialize();
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new InvalidObjectException("Illegal load factor: " +
                                         loadFactor);
    s.readInt();                // Read and ignore number of buckets
    int mappings = s.readInt(); // Read number of mappings (size)
    if (mappings < 0)
        throw new InvalidObjectException("Illegal mappings count: " +
                                         mappings);
    else if (mappings > 0) { // (if zero, use defaults)
        // Size the table using given load factor only if within
        // range of 0.25...4.0
        // 这一部分确定表的大小,以及下一次resize的阈值
        float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
        float fc = (float)mappings / lf + 1.0f;
        int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
                   DEFAULT_INITIAL_CAPACITY :
                   (fc >= MAXIMUM_CAPACITY) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor((int)fc));
        float ft = (float)cap * lf;
        threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
                     (int)ft : Integer.MAX_VALUE);

            // Check Map.Entry[].class since it's the nearest public type to
            // what we're actually creating.
        SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap);
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
        table = tab;
        
        // 这一部分读取并插入元素
        // Read the keys and values, and put the mappings in the HashMap
        for (int i = 0; i < mappings; i++) {
            @SuppressWarnings("unchecked")
            K key = (K) s.readObject();
            @SuppressWarnings("unchecked")
            V value = (V) s.readObject();
            putVal(hash(key), key, value, false, false);
        }
    }
}

总结

  • HashMap允许键和值都为null,不保证元素能按照插入的顺序被访问,也不保证访问顺序不发生变化。
  • 表的长度始终是2的幂,在两种情况下会出现resize:1)当size >= table.length * loadFactor时;2)当链表长度大于等于8(需要树化),但表的长度小于64时。但是如果表的长度达到最大值(2的30次方),表不会扩展。
  • 在两种情况下会出现由树转化成链表的情况:1)resize的时候,节点数量小于等于6;2)删除树节点时,节点数量过少(0 - 2)。