JDK 1.8 之 Map.merge()

2022-10-14,,,

mapconcurrenthashmap是线程安全的,但不是所有操作都是,例如get()之后再put()就不是了,这时使用merge()确保没有更新会丢失。

因为map.merge()意味着我们可以原子地执行插入或更新操作,它是线程安全的。

一、源码解析

default v merge(k key, v value, bifunction<? super v, ? super v, ? extends v> remappingfunction) {
    objects.requirenonnull(remappingfunction);
    objects.requirenonnull(value);
    v oldvalue = get(key);
    v newvalue = (oldvalue == null) ? value :
               remappingfunction.apply(oldvalue, value);
    if(newvalue == null) {
        remove(key);
    } else {
        put(key, newvalue);
    }
    return newvalue;
}

该方法接收三个参数,一个 key 值,一个 value,一个 remappingfunction 。如果给定的key不存在,它就变成了put(key, value);但是,如果key已经存在一些值,我们 remappingfunction 可以选择合并的方式:

  1. 只返回新值即可覆盖旧值: (old, new) -> new;
  2. 只需返回旧值即可保留旧值:(old, new) -> old;
  3. 合并两者,例如:(old, new) -> old + new;
  4. 删除旧值:(old, new) -> null

二、使用场景

merge()方法在统计时用的场景比较多,例如:有一个学生成绩对象的列表,对象包含学生姓名、科目、科目分数三个属性,求得每个学生的总成绩。

2.1 准备数据

  • 学生对象studententity.java
@data
public class studententity {
    /**
     * 学生姓名
     */
    private string studentname;
    /**
     * 学科
     */
    private string subject;
    /**
     * 分数
     */
    private integer score;
}
  • 学生成绩数据
private list<studententity> buildatestlist() {
    list<studententity> studententitylist = new arraylist<>();
    studententity studententity1 = new studententity() {{
        setstudentname("张三");
        setsubject("语文");
        setscore(60);
    }};
    studententity studententity2 = new studententity() {{
        setstudentname("张三");
        setsubject("数学");
        setscore(70);
    }};
    studententity studententity3 = new studententity() {{
        setstudentname("张三");
        setsubject("英语");
        setscore(80);
    }};
    studententity studententity4 = new studententity() {{
        setstudentname("李四");
        setsubject("语文");
        setscore(85);
    }};
    studententity studententity5 = new studententity() {{
        setstudentname("李四");
        setsubject("数学");
        setscore(75);
    }};
    studententity studententity6 = new studententity() {{
        setstudentname("李四");
        setsubject("英语");
        setscore(65);
    }};
    studententity studententity7 = new studententity() {{
        setstudentname("王五");
        setsubject("语文");
        setscore(80);
    }};
    studententity studententity8 = new studententity() {{
        setstudentname("王五");
        setsubject("数学");
        setscore(85);
    }};
    studententity studententity9 = new studententity() {{
        setstudentname("王五");
        setsubject("英语");
        setscore(90);
    }};

    studententitylist.add(studententity1);
    studententitylist.add(studententity2);
    studententitylist.add(studententity3);
    studententitylist.add(studententity4);
    studententitylist.add(studententity5);
    studententitylist.add(studententity6);
    studententitylist.add(studententity7);
    studententitylist.add(studententity8);
    studententitylist.add(studententity9);

    return studententitylist;
}

2.2 一般方案

思路:用map的一组key/value存储一个学生的总成绩(学生姓名作为key,总成绩为value)

  1. map中不存在指定的key时,将传入的value设置为key的值;
  2. key存在值时,取出存在的值与当前值相加,然后放入map中。
public void normalmethod() {
    long starttime = system.currenttimemillis();
    // 造一个学生成绩列表
    list<studententity> studententitylist = buildatestlist();

    map<string, integer> studentscore = new hashmap<>();
    studententitylist.foreach(studententity -> {
        if (studentscore.containskey(studententity.getstudentname())) {
            studentscore.put(studententity.getstudentname(),
                    studentscore.get(studententity.getstudentname()) + studententity.getscore());
        } else {
            studentscore.put(studententity.getstudentname(), studententity.getscore());
        }
    });
    log.info("各个学生成绩:{},耗时:{}ms",studentscore, system.currenttimemillis() - starttime);
}

2.3 map.merge()

很明显,这里需要采用remappingfunction的合并方式。

public void mergemethod() {
    long starttime = system.currenttimemillis();
    // 造一个学生成绩列表
    list<studententity> studententitylist = buildatestlist();
    map<string, integer> studentscore = new hashmap<>();
    studententitylist.foreach(studententity -> studentscore.merge(
            studententity.getstudentname(),
            studententity.getscore(),
            integer::sum));
    log.info("各个学生成绩:{},耗时:{}ms",studentscore, system.currenttimemillis() - starttime);
}

2.4 测试及小结

  • 测试方法
@test
public void testall() {
    // 一般写法
    normalmethod();
    // merge()方法
    mergemethod();
}
  • 测试结果
00:21:28.305 [main] info cn.van.jdk.eight.map.merge.mapofmergetest - 各个学生成绩:{李四=225, 张三=210, 王五=255},耗时:75ms
00:21:28.310 [main] info cn.van.jdk.eight.map.merge.mapofmergetest - 各个学生成绩:{李四=225, 张三=210, 王五=255},耗时:2ms
  • 结果小结
  1. merger()方法使用起来在一定程度上减少了代码量,使得代码更加简洁。同时,通过打印的方法耗时可以看出,merge()方法效率更高。
  2. map.merge()的出现,和concurrenthashmap的结合,完美处理那些自动执行插入或者更新操作的单线程安全的逻辑.

三、总结

3.1 示例源码

github 示例代码

3.2 技术交流

  1. 风尘博客-csdn

《JDK 1.8 之 Map.merge().doc》

下载本文的Word格式文档,以方便收藏与打印。