欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JDK 1.8 之 Map.merge()

程序员文章站 2023-11-22 23:58:22
Map 中 ConcurrentHashMap是线程安全的,但不是所有操作都是,例如 get() 之后再 put() 就不是了,这时使用 merge() 确保没有更新会丢失。因为Map.merge()意味着我们可以原子地执行插入或更新操作,它是线程安全的。 ......

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