【Java集合】HashMap的remove()源码详解以及JDK1.7与JDK1.8的区别

本文详细分析了HashMap在JDK1.8和1.7中remove()方法的执行流程和源码实现。1.8版引入了红黑树结构,删除操作更加高效。主要步骤包括:计算hash值,遍历查找目标节点,匹配key和value(1.8版可选),并移除节点,更新map结构和大小。1.7版则通过链表实现,遍历找到目标节点后直接删除。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、remove()方法的执行流程

二、remove()方法源码

三、JDK1.7的remove()方法源码


一、remove()方法的执行流程

流程图:

二、remove()方法源码

HashMap中有两个remove()方法,一般常用的是第一个

1、以key为参数的remove方法 输入key–>key存在就删除,若删除成功则返回被删除的元素的value,删除失败返回null

public V remove(Object key) {
    // 被删除的元素
    Node<K,V> e;
       // 返回移除的节点的value值
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

2、以key+value为参数的remove方法 必须key和value都相同才删除

public boolean remove(Object key, Object value) {
	// 返回是否成功移除节点
    return removeNode(hash(key), key, value, true, true) != null;
}

HashMap.remove(key) 不去判断值相不相等。

HashMap.EntrySet.remove(key)、HashMap.remove(key,value)、LinkedHashMap.LinkedEntrySet.remove(key)需要判断value相不相等

但是两个方法都是在调用removeNode()方法

/**
    移除某个节点,根据下面四个条件进行移除
    hash - key 的hash值 
    key - key
    matchValue - 如果为true,则仅在值相等时删除;如果是false,则值不管相不相等,只要key和hash值一致就移除该节点。
    movable - 如果为false,则在删除时不移动其他节点
    return - 返回被移除节点,未找到则返回null
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    // tab:当前map 的数组,p:hash对应的数组索引index位置上的节点/遍历链表时表示当前遍历到的节点的前一个节点,n:数组长度,index:hash对应的数组索引
    // 这几个值在hashMap的源码中很常见
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    // 前提判断 数组不为空,并且长度大于0 并且
    // hash对应的数组索引位置上的节点p也不为null
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        // node:被移除的节点,e:当前头节点的下一个节点/遍历链表时表示当前遍历到的节点,
        // k:e节点的key,v:被移除节点node 的value
        Node<K,V> node = null, e; K k; V v;
        // 如果第一个节点p就是目标节点,则将node指向第一个节点p
        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){
                // 调用红黑树的查询节点的方法,getTreeNode()
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            }
            // 第一个节点不是红黑树,并且还有第二个节点,那就说明,这里是链表了
            else {
                // 那么开始循环链表,从第二个节点开始循环,因为第一个节点已经处理过了
                do {
                    // 判断e节点是不是目标节点,是的话就将node指向e,并且终止循环
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    // e节点不是目标节点,那就将p节点指向e节点,
                    // 然后while里面e节点后移,在进入循环后发现e是目标节点了,退出循环,退出后此时p节点还是e节点的前一个节点,也就保证了在整个循环的过程中,p节点始终是e节点的前一个节点。
                    p = e;
                } while ((e = e.next) != null);// e指针后移,并且下一个节点不为null则继续遍历,不为null表示没到链表最后。
            }
        }
        // 找到目标节点了  matchValue为true,则仅在值相等时删除。如果是false,则值不管相不相等,只要key和hash值一致就移除该节点。
        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);
            }
            // 目标节点是p节点,
            // 还记得之前 如果第一个节点p(数组桶中的节点)就是目标节点,则将node指向第一个节点p
            else if (node == p){
                // 将目标节点的下一个节点作为该索引位置的第一个元素
                // 也就是跳过目标节点,指向目标节点的下一位
                tab[index] = node.next;
            }
            // 这里就是遍历链表找到了目标节点
            else{
                // p节点始终作为node的上一个节点,p.next始终指向目标节点node
                // 现在将p.next 指向目标节点node的next,这样跳过了目标节点node,就把node移除掉了
                p.next = node.next;
            }
            
            // 记录map结构被修改的次数,主要用于并发编程
            ++modCount;
            // 记录table存储了多少键值对,因为移除了一个,所以此处就减一
            --size;
            // 该方法在hashMap中是空方法,主要是供LinkedHashMap使用,因为LinkedHashMap重写了该方法
            afterNodeRemoval(node);
            // 返回被移除的节点
            return node;
        }
    }
    // 没找到 返回null
    return null;
}

三、JDK1.7remove()方法源码

流程与JDK1.8基本一致,只是没有红黑树结构

/**
 * 函数:remove(Object key)
 * 作用:删除该键值对
 * @return 返回被删除的value值,如果删除失败返回Null 
 */ 
public V remove(Object key) {  
    Entry<K,V> e = removeEntryForKey(key);  
    // 返回移除的节点的value值
    return (e == null ? null : e.value);  
}  
  
final Entry<K,V> removeEntryForKey(Object key) {
    // HashMap为空,直接返回Null  
    if (size == 0) {  
        return null;  
    }  
    // 1. 计算hash值
    int hash = (key == null) ? 0 : hash(key);  
    // 2. 计算存储的数组下标位置
    int i = indexFor(hash, table.length);  
    // 定位到key值对应的数组位置
    // prev在后面遍历链表时指向当前节点的前一个节点,e表示当前遍历到的节点
    Entry<K,V> prev = table[i];  
    Entry<K,V> e = prev;  
    // 遍历链表,找到key和hash值与传入参入相同的节点,即为要删除的节点
    while (e != null) { 
        // 记录当前节点的下一个节点 
        Entry<K,V> next = e.next;  
        Object k;  
        // 如果key值和hash值相同,则找到目标节点。执行移除操作
        if (e.hash == hash &&  
            ((k = e.key) == key || (key != null && key.equals(k)))) {  
            // 记录map结构被修改的次数,主要用于并发编程
            modCount++;  
            // 记录table存储了多少键值对,因为移除了一个,所以此处就减一
            size--; 
            // 若删除的是table数组中的元素(即链表的头结点) 
            // 则删除操作 = 将头结点的next引用存入table[i]中  
            if (prev == e) 
                table[i] = next;
            //否则 将以table[i]为头结点的链表中,当前Entry的前1个Entry中的next 设置为 当前Entry的next(即删除当前Entry = 直接跳过当前Entry)
            else  
                prev.next = next;   
            e.recordRemoval(this);  
            return e;  
        }  
        prev = e;  
        e = next;  
    }  
    
    // 返回删除的节点
    return e;  
} 

 

 相关文章: 【Java集合】HashMap系列(一)——底层数据结构分析
                   【Java集合】HashMap系列(二)——底层源码分析
                   【Java集合】HashMap的tableSizeFor()源码详解​​​​​​
                   【Java集合】HashMap的putMapEntries()源码详解 
                   【Java集合】HashMap的put()源码详解以及JDK1.7与JDK1.8的区别 
                   【Java集合】HashMap的get()源码详解以及JDK1.7与JDK1.8的区别 
                   【Java集合】HashMap的resize()源码详解以及JDK1.7与JDK1.8的区别​​​​​​​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值