Map中的值自增自减或重新操作值的最优雅写法,无需调用get()和put(),包含HashMap compute()源码分析

本文介绍JDK8中HashMap的compute()和merge()方法,如何避免get和put操作,提升代码效率和可读性。学习如何利用compute()进行键值操作,以及merge()的简洁用法及其原理分析。
摘要由CSDN通过智能技术生成

相信各位在开发的过程中总能遇见类似这样的需求:查询Map中的某一key值,如果有就将值加一,没有就put新的key值为1 (或对值进行其他操作)。

通常大多数人的写法是这样的:

    @Test
    public void test1() {
        HashMap<Integer, Integer> map = new HashMap();
        map.put(1, 1); // {1=1}

        for (int i = 0; i < 3; i++) {
            if (null == map.get(i)) {
                map.put(i, 1);
                continue;
            }
            int oldNum = map.get(i);
            map.put(i, ++oldNum);
        }
        System.out.println(map); // {0=1, 1=2, 2=1}
    }

这种写法虽然可以实现,但是先get()在put()的操作感觉很是笨拙。

那么有没有一种更优雅美观的方式,在不调用map的get() 和 put() 方法对map中的值进行操作呢?

下面我们就要开始我们今天的重头戏了 : JDK8中新加入的compute()方法

首先来看下使用comput()方法实现上面需求的代码:

@Test
    public void test2() {
        HashMap<Integer, Integer> map = new HashMap();
        map.put(1, 1); // {1=1}

        for (int i = 0; i < 3; i++) {
            map.compute(i, (k, v) -> {
                if (v == null) {
                    return 1;
                }
                return ++v;
            });
        }
        System.out.println(map); // {0=1, 1=2, 2=1}
    }

此方法减少了两次get和一次put,而是使用一个compute() 完美解决。而且使用也非常简单,只需在第一个参数位置填我们要查询的key值,在第二个参数重写我们要对value的操作即可,return的值会重新赋值这个key。(注意是++v不是v++,v++实际上是在赋值了原来的value后再将v自增)


compute() 方法的语法为:

hashmap.compute(K key, BiFunction remappingFunction)
  • key - 键
  • remappingFunction - 重新映射函数,用于重新计算值
  • 返回值 :
    • 原 value 不存在,计算的新value为null,则返回 null 并不进行操作
    • 原 value 不存在,计算的新 value 不为null,添加新的键值对,返回新value
    • 原 value 存在,计算的新 value 为null,删除原值,返回新value
    • 原 value 存在,计算的新 value 不为null,重新赋值并返回通过 remappingFunction 重新计算后的值

compute()源码分析(jdk_1.8.0_251):

@Override
    public V compute(K key,
                     BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        if (remappingFunction == null)
            throw new NullPointerException(); // 输入的remappingFunction为null抛出异常
        int hash = hash(key); // 计算hash值
        Node<K,V>[] tab; Node<K,V> first; int n, i;
        int binCount = 0;
        TreeNode<K,V> t = null;
        Node<K,V> old = null;
        // 如果当前Map需要扩容,或者元素的数组为null,或者元素数组长度为0,则初始化元素数组
        // tab = resize() 重新初始化元素数组(也可以用来扩容)
        if (size > threshold || (tab = table) == null ||
            (n = tab.length) == 0) 
            n = (tab = resize()).length;
        // map是数组加链表或数组加红黑树的数据结构,所以first是链表上的第一个元素,如果firsh不为null则进如if
        if ((first = tab[i = (n - 1) & hash]) != null) {
        	// 如果是TreeNode的子类(红黑树数据结构),则通过方法获取原Node
            if (first instanceof TreeNode)
                old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
                
            // 链表数据结构,
            else {
            	// e用来指向链表的元素
                Node<K,V> e = first; K k;
                do {
                	// 如果hash值相等 并且key值相等 并且(key不为null,equals方法为true),则获取到原Node
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) {
                        old = e;
                        break;
                    }
                    // 记录链表的元素个数,用来之后判断是否超出阈值将链表转换成红黑树
                    ++binCount;
                } while ((e = e.next) != null);
            }
        }
        // oldvalue:原value
        V oldValue = (old == null) ? null : old.value;
        // v:根据我们实现的方法来计算新的value
        V v = remappingFunction.apply(key, oldValue);
        if (old != null) {
            if (v != null) {
            	// 原map有key对应的键值对,则用计算出的新值赋值
                old.value = v;
                // 元素被访问之后的后置处理, LinkedHashMap中有具体实现
                afterNodeAccess(old);
            }
            else
            	// 原map有key对应的键值对,但是新计算的值为null,删除原值。
                removeNode(hash, key, null, false, true);
        }
        // 新value不为null
        else if (v != null) {
        	// 原map没有key对应的键值对,并且当前桶使用红黑树数据结构存储,添加此键值对
            if (t != null)
                t.putTreeVal(this, tab, hash, key, v);
            else {
            // 原map没有key对应的键值对,并且当前桶使用链表数据结构存储,添加此键值对
                tab[i] = newNode(hash, key, v, first);
                // 查看是否到设置的链表转红黑树的阈值,到达则将链表转换成红黑树
                if (binCount >= TREEIFY_THRESHOLD - 1)
                    treeifyBin(tab, hash);
            }
            // 已知对hashMap结构修改的次数,结构修改指更改hashMap的映射数或以其他方式更改其内部结构(例:重新hash)的修改,此字段用于是hashMap的collectionview迭代器快速失败
            ++modCount;
            // hashmap大小(键值对数) ++
            ++size;
            // 添加新元素之后的后置处理, LinkedHashMap中有具体实现
            afterNodeInsertion(true);
        }
        return v;
    }

merge()方法类似与comoute()方法但是更简洁:

使用merge()实现上面需求

@Test
    public void test2() {
        HashMap<Integer, Integer> map = new HashMap();
        map.put(1, 1); // {1=1}

         for (int i = 0; i < 3; i++) {
            map.merge(i, 1, (oldvalue, newvalue) -> ++oldvalue);
        }
        System.out.println(map); // {0=1, 1=2, 2=1}
    }

第一个参数为key
第二个参数是一个newValue
第三个参数是对新值和旧值的操作
如果原value为null则添加key和第二个参数值newValue,如果原value不为null则赋值计算后的值(newValue为merge()方法的第二个参数值newValue)


类似compute()方法的其他方法:

computeIfPresent(): 如果map存在key,则对老value进行操作计算后赋值
computeIfAbsent(): 如果map不存在key,则对使用计算后的新value进行赋值


merge(),computeIfPresent(),computeIfAbsent()可以自行查看hashmap源码,和上面的compute()源码类似。

到此我们对JDK8中新加入的几种对hashMap的操作的方法就学习完了,这几种方法可以让我们更加便捷的对map中的key-value进行操作,省去了先get在put的繁琐操作,让我们在开发的过程中使代码更加简洁流畅。

如果此篇文章对你有帮助可以关注我呦!我会继续更新自己平时所学习的java知识!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值