HashMap源码阅读

一、简介

HashMap是开发中用的非常多的一个哈希表数据结构,HashMap类位于java.util包中。下面对HashMap做一个简介:

  • HashMap是一个用于存储Key-Value键值对的集合,底层使用数组 + 链表 + 红黑树实现,每一个键值对也叫做Entry,这些键值对(Entry)分散存储在一个数组中。HashMap数组的每一个元素不止是一个Entry对象,也是一个链表的头节点。每一个Entry对象通过Next指针指向它的下一个Entry节点。
  • HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap 类大致相当于Hashtable,只是它是不同步,允许为空。
  • 一般来说,默认的负载因子(0.75)在时间和空间成本之间提供了很好的权衡。更高的值减少了空间开销,但是增加了查找成本(反映在HashMap类的大多数操作中,包括get和put)。在设置map的初始容量时,应该考虑map中条目的期望数量及其负载因子,从而最小化rehash操作的数量。如果初始容量大于条目的最大数量除以负载因子,则不会发生重排操作。
  • 注意HashMap实现不是同步的。如果多个线程同时访问一个散列映射,可能存在线程安全问题。
  • 为了防止意外对映射的非同步访问,可以使用:Map m = Collections.synchronizedMap(new HashMap(...));

下图为HashMap结构简化图:

下面我们去看看HashMap在JDK1.8中的实现:

  • HashMap继承自抽象类AbstractMap,并且实现了Map<K,V>, Cloneable, Serializable接口:
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
        //......
    }
  • 属性
//序列化ID
private static final long serialVersionUID = 362498820763181265L;

//默认的初始容量-必须是2的幂。
//1<<4左移4位,相当于2 * 2 * 2 * 2 = 16,即默认初始容量为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

//最大容量,如果更高的值是由任何一个带有参数的构造函数隐式指定的,则使用该值。必须是2的幂<= 1<<30。
static final int MAXIMUM_CAPACITY = 1 << 30;

//在构造函数中没有指定时使用的负载因子。
//默认负载因子为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//使用树而不是列表的容器计数阈值。当向至少有这么多节点的bin中添加元素时,bin将被转换为树。该值必须大于2,并且应该至少为8,以便与树木移除时关于收缩后转换回普通桶的假设相吻合。
//当链表的长度大于8时,并且桶的数量大于64(MIN_TREEIFY_CAPACITY)时,链表会转换为红黑树结构
static final int TREEIFY_THRESHOLD = 8;

//在调整大小操作期间取消(拆分)存储的存储库计数阈值。应小于TREEIFY_THRESHOLD,且最多6个网格进行收缩检测下去除。
//当链表的长度小于6时,红黑树结构会转换为链表结构
static final int UNTREEIFY_THRESHOLD = 6;

//可以对容器进行treeified的最小表容量。(否则,如果一个bin中有太多节点,就会重新调整表的大小。)至少4 * TREEIFY_THRESHOLD,以避免调整大小和treeification阀值之间的冲突。
static final int MIN_TREEIFY_CAPACITY = 64;

//哈希表,第一次使用时初始化,并根据需要调整大小。当分配时,长度总是2的幂。(在某些操作中,我们还允许长度为零,以允许当前不需要的引导机制。)
//哈希表结构(链表散列:数组+链表)实现当链表超过8且数据总量超过64才会转红黑树
transient Node<K,V>[] table;

//保存缓存entrySet ()。注意,AbstractMap字段用于keySet()和values()。
transient Set<Map.Entry<K,V>> entrySet;

//map包含的键值映射的数量(实际存在的键值对数量)
transient int size;

//用来记录HashMap内部结构发生变化的次数,主要用于迭代的快速失败
transient int modCount;

//所能容纳的key-value对的最大数量
//在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多 
int threshold;

//哈希表的加载因子。
final float loadFactor;
  • Node类:基本的哈希bin节点,实质就是键值对。
//实现 Map.Entry<K,V>接口,本质是就是一个映射(键值对)
static class Node<K,V> implements Map.Entry<K,V> {
    //哈希值,用来定位数组索引位置
    final int hash;
    //键
    final K key;
    //值
    V value;
    //链表的下一个Node
   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; }
    //toString()输出方法
    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<?,?>
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            //键相同并且对应的值也相同,返回true,否则返回false
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}
  • 构造方法
//使用指定的初始容量和负载因子构造一个空的HashMap。
public HashMap(int initialCapacity, float loadFactor) {
    //如果初始容量小于0,抛出非法参数异常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    //初始容量最大只能为2的30次方,即1073741824
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
     //如果负载因子小于等于0或者为NaN,抛出非法参数异常
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    //返回给定目标容量的两倍幂。
    this.threshold = tableSizeFor(initialCapacity);
}

//返回给定目标容量的两次幂。 initialCapacity <= 2的n次幂
//假设cap = 12
static final int tableSizeFor(int cap) {
    // n = 12 - 1 = 11
    int n = cap - 1;
           // 0000 1011 = 11
//往右移一位: 0000 0101 = 5
// 或运算之后:0000 1111 = 15,所以此时n = 15
    n |= n >>> 1;
    //以此类推..
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

//构造一个空的HashMap,具有指定的初始容量和默认负载因子(0.75)。
//initialCapacity:初始化容量
public HashMap(int initialCapacity) {
    //使用默认负载因子0.75
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

//构造一个空的HashMap,默认初始容量(16)和默认负载因子(0.75)。
public HashMap() {
    //赋值默认负载因子
    this.loadFactor = DEFAULT_LOAD_FACTOR; 
}

//使用与指定的Map相同的映射构造一个新的HashMap。HashMap是使用默认负载因子(0.75)和足够容纳指定Map中的映射的初始容量创建的。
public HashMap(Map<? extends K, ? extends V> m) {
    //使用默认负载因子0.75
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}

//evict: 在最初构造这个映射时为false,否则为true
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    //判断当前map的大小
    if (s > 0) {
        //判断当前线性表Node<K,V>[] table数组是否为空
        if (table == null) { // pre-size
            // 容量 = (容量/ 负载因子) + 1
            // 初始化容量
            float ft = ((float)s / loadFactor) + 1.0F;
            // 判断是否超过最大容量
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
               //返回给定目标容量t的两倍幂。
                threshold = tableSizeFor(t);
        }
        else if (s > threshold)
            //扩容操作
            resize();
        //循环遍历当前map中所有的键值对
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            //获取键和值,再重新放进去
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}
  • hash()
//计算key的哈希值
static final int hash(Object key) {
    int h;
    //如果key不为空,使用key的哈希码与(key哈希码进行右移16位)进行异或运算(相同为0,相异为1)
    //目的是提高hashcode的随机性,有效减小hash冲突率(让高位参与下标的计算)
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • resize()
//扩容
//生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能.
//如果数据很大的情况下,扩展时将会带来性能的损失,在性能要求很高的地方,这种损失很可能很致命。
final Node<K,V>[] resize() {
    //老的线性表初始值
    Node<K,V>[] oldTab = table;
    //扩容前:旧容量
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //扩容前:旧阈值
    int oldThr = threshold;
    //扩容后:newCap新容量, newThr新阈值
    int newCap, newThr = 0;
    
    //判断旧容量是否大于0
    if (oldCap > 0) {
        //是否超过最大容量1073741824
        if (oldCap >= MAXIMUM_CAPACITY) {
            //超过最大容量,无法扩容,将阈值设置为整数最大值,直接返回旧容量
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //如果新容量(旧容量的2倍)是否大于最大容量,并且旧容量是否大于等于默认容量16
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            //设置新阈值是旧阈值的2倍
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        //如果旧容量小于0,并且旧阈值大于0,则设置新容量为旧阈值
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        //初始阈值为零表示使用默认值
        //新容量为默认容量 = 16
        newCap = DEFAULT_INITIAL_CAPACITY;
        //新阈值 = 默认负载因子 * 默认初始化容量 = 0.75 *16 = 12
        //比如table 数组大小为 16,装载因子为 0.75 时,threshold 就是12,当 table 的实际大小超过 12 时,table就需要动态扩容;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    
    //如果新阈值为0,
    if (newThr == 0) {
        //阈值 = 默认负载因子 * 新容量
        float ft = (float)newCap * loadFactor;
        //新阈值最大只能为Integer.MAX_VALUE
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    //为当前的容量阈值赋值
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        //创建一个新容量newCap的数组
        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;
            //获取j位置的元素
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                //判断原来j位置上是否存在元素
                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
                    // 链表优化重hash的代码块
                    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)
                                //不存在尾部元素,则将e作为头部元素
                                loHead = e;
                            else
                                //存在尾部元素,则将e拼接上尾部
                                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;
}

二、常用API

【a】size()、isEmpty()、get()

//返回map中的键值映射的数目
public int size() {
    return size;
}

//如果此映射不包含键值映射,则返回true,否则返回false
public boolean isEmpty() {
    return size == 0;
}

//返回指定键映射到的值,如果此映射不包含键的映射,返回null。
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

//根据哈希码和key返回对应的节点
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        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);
                //顺序遍历链表,equals()方法查找相同 Node 链表中 K 值对应的 V 值。
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

【b】containsKey()

//如果此映射包含指定键的映射,则返回<tt>true</tt>。
public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

【c】put()

//将指定值与此映射中的指定键关联。如果映射之前包含键的映射,则替换旧值。
public V put(K key, V value) {
    //根据key获取hashcode值
    return putVal(hash(key), key, value, false, true);
}

//onlyIfAbsent: false   evict: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)
        //扩容,初始化table的长度
        n = (tab = resize()).length;
        
    //假设n = 16,则((n - 1) & hash) = (15 & hash) = i
    //计算出来的i就是元素在线性表数组tab中的下标
    // p = tab[i] = Node<K,V>
    // 其实p相当于是链表。
    // (n - 1) & hash其实是求余运算,为了提高计算性能
    if ((p = tab[i = (n - 1) & hash]) == null)
        //创建新节点,直接放在tab[i]位置
        tab[i] = newNode(hash, key, value, null);
    else {
        //tab[i]链表上已经存在元素
        Node<K,V> e; K k;
        //判断p的hash是否等于当前的hash值, 节点key存在,直接覆盖value
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            //判断table[i]中的元素是否与插入的key一样,若相同那就直接使用插入的值p替换掉旧的值e。
            e = p;
        //判断是否是树结构(JDK1.8提出红黑树优化方案)        
        else if (p instanceof TreeNode)
            //基于红黑树的插入逻辑, 直接在树中插入键值对
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            //链表插入元素
            for (int binCount = 0; ; ++binCount) {
                //判断p的下一个元素是否为空
                if ((e = p.next) == null) {
                    //新创建节点,设置p的下一个元素为新节点
                    p.next = newNode(hash, key, value, null);
                    //判断当前链表的数量是否大于树结构的阈值
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        //转换数据结构,将链表转换为红黑树,目的是优化查询性能
                        //当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树
                        treeifyBin(tab, hash);
                    break;
                }
                //当前链表包含需要插入的值,跳出循环
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    //修改次数+1
    ++modCount;
    //判断当前数组大小是否大于阈值,如果大于阈值,需要进行扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

//将指定映射的所有映射复制到此映射。这些映射将替换当前指定映射中任何键的映射。
public void putAll(Map<? extends K, ? extends V> m) {
    putMapEntries(m, true);
}

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        if (table == null) { // pre-size
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        else if (s > threshold)
            resize();
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}

【d】remove()

//如果存在,则从此映射中删除指定键的映射。
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;
    
    //判断线性表是否为空或者长度是否为0
    // index = (n - 1) & hash
    // p = tab[index] 是否为空
    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;
        
        //hash相同并且key相同,说明hash没有冲突的情况
        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 {
                //链表的删除
                //循环遍历链表,定位要删除的节点node 
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        
        //node就是需要删除的元素
        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元素的下一个节点指向node节点的下一个节点,这样就跳过了node节点,达到删除的效果
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

【e】clear()

//从该映射中删除所有映射。这个调用返回后,映射将为空。
public void clear() {
    Node<K,V>[] tab;
    modCount++;
    //如果table不为空时
    if ((tab = table) != null && size > 0) {
        //将长度置为0
        size = 0;
        //循环遍历table,将每个Node置空
        for (int i = 0; i < tab.length; ++i)
            tab[i] = null;
    }
}

 【f】containsValue()

//如果此映射将一个或多个键映射到指定的值,则返回true。
public boolean containsValue(Object value) {
    Node<K,V>[] tab; V v;
    //当table不为空时
    if ((tab = table) != null && size > 0) {
        //循环tab
        for (int i = 0; i < tab.length; ++i) {
            //依次拿出每一个Node<K,V>节点,然后再遍历链表,拿出value挨个比较
            for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                if ((v = e.value) == value ||
                    (value != null && value.equals(v)))
                    return true;
            }
        }
    }
    //如果table为空,直接返回false
    return false;
}

【g】keySet()

//返回包含在此映射中的键key的Set集合
public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        //重新new一个KeySet
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}

final class KeySet extends AbstractSet<K> {
    public final int size()                 { return size; }
    public final void clear()               { HashMap.this.clear(); }
    public final Iterator<K> iterator()     { return new KeyIterator(); }
    public final boolean contains(Object o) { return containsKey(o); }
    public final boolean remove(Object key) {
        return removeNode(hash(key), key, null, false, true) != null;
    }
    public final Spliterator<K> spliterator() {
        return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    public final void forEach(Consumer<? super K> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e.key);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

【h】values()

//返回包含在map中的值的集合。
public Collection<V> values() {
    Collection<V> vs = values;
    if (vs == null) {
        vs = new Values();
        values = vs;
    }
    return vs;
}

final class Values extends AbstractCollection<V> {
    public final int size()                 { return size; }
    public final void clear()               { HashMap.this.clear(); }
    public final Iterator<V> iterator()     { return new ValueIterator(); }
    public final boolean contains(Object o) { return containsValue(o); }
    public final Spliterator<V> spliterator() {
        return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    public final void forEach(Consumer<? super V> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e.value);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

【i】entrySet()

//返回包含在map中的键值对的映射的集合。
public Set<Map.Entry<K,V>> entrySet() {
    Set<Map.Entry<K,V>> es;
    return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}

final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    public final int size()                 { return size; }
    public final void clear()               { HashMap.this.clear(); }
    public final Iterator<Map.Entry<K,V>> iterator() {
        return new EntryIterator();
    }
    public final boolean contains(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>) o;
        Object key = e.getKey();
        Node<K,V> candidate = getNode(hash(key), key);
        return candidate != null && candidate.equals(e);
    }
    public final boolean remove(Object o) {
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Object value = e.getValue();
            return removeNode(hash(key), key, value, true, true) != null;
        }
        return false;
    }
    public final Spliterator<Map.Entry<K,V>> spliterator() {
        return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

 三、总结

总结一下HashMap需要搞懂的几点,也是面试常问到的点:

  • 初始容量是多少?为啥初始容量一定要是2的n次幂? (高位参与运算)
  • hash()求哈希码函数的实现方式? (key.hashcode() ^ (key.hashcode() >>> 16))
  • resize() 自动扩容的处理流程?(创建新数组,将旧数组的数据移动到新数组中,rehash操作)
  • hash冲突时的解决方案(链表-拉链法)? (链表尾插)
  • put() 获取数组下标的方法? hash(key) & (length - 1)? (确保hash尽量分散均匀)

本文是笔者第一次阅读HashMap源码时的一些总结,其中涉及到红黑树部分的暂时还没有去研究,后面学习完数据结构以及算法之后会重新再补充这部分的内容,HashMap源码相对比较复杂,得多看几遍才能领悟其中设计的妙处,希望下次再读的时候会有一些新的见解和认识。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值