一个算法带来的反思+Map复杂方法的使用总结

发布时间:2024年01月12日

1. 起因

任务是这样的,想要通过数据库查到两张表的数据,然后通过关联的id给对象赋值,再通过对象的某个字段进行分组,最终放入大对象中。

放在下面就是获取PeoplePosition,然后通过id,将对应的职位放入People中,分组的结果放入PositionObj 构建的列表中。

2. 准备

相关代码如下所示,这里模拟了数据库的查询。

public class MergeAlorithm {

    public static void main(String[] args) {
        // 先通过设置map 再通过map给对象设值 然后通过该对象 进行分组 放入大对象中
        List<Position> positiones = getPositiones();
        Map<Integer, String> map = positiones.stream().collect(Collectors.toMap(Position::getId, Position::getPosition));
        
        List<People> peoples = getPeopleList();
		// 补充代码
    }
    
    
    // 假装是从db中获取的people列表
    public static List<People> getPeopleList() {
        return  Lists.newArrayList(
                new People(4, "小a", null),
                new People(2, "小b", null),
                new People(2, "小c", null),
                new People(6, "小d", null)
        );
    }
    
    // 假装是从db中获取的职位列表
    public static List<Position> getPositiones() {
        return Lists.newArrayList(
                new Position(1, "班长"),
                new Position(2, "小组长"),
                new Position(3, "委员")
        );
    }
    
}

@Data
@AllArgsConstructor
class People {
    private Integer id;
    private String name;
    private String position;

}
@Data
@AllArgsConstructor
class Position {
    private Integer id;
    private String position;

}

@Data
@AllArgsConstructor
class PositionObj {
    private String position;
    private List<People> peoples;
}

3. 实现

3.1 使用流分组

通过循环设值,然后通过流分组,最后设置对象,看似没什么问题,但是多了一次循环,能不能再优化呢?

        // 1. 设置 外层再分组
        // 循环 通过map 设值 总共三次循环 
        for (People people : peoples) {
            String position = map.getOrDefault(people.getId(), "无");
            people.setPosition(position);
        }
        // 通过流 再遍历  
        Map<String, List<People>> positionMap = peoples.stream().collect(Collectors.groupingBy(People::getPosition));
        //再放到一个对象中
        List<PositionObj> positionObjs = positionMap.entrySet().stream().map(entry -> new PositionObj(entry.getKey(), entry.getValue()))
                .collect(Collectors.toList());
        System.out.println(JSON.toJSON(positionObjs));

3.2 使用computeIfAbsent方法

可以看到,我们在第一次循环中,直接进行了分组,而使用computeIfAbsent可以先检查对应的k是否在map中,存在则关联对应的列表,放入people,如果不存在则创建新列表放入people,完美符合我们的要求。
positionMap.computeIfAbsent(position, k -> new ArrayList<>()).add(people);
关键就是这一行代码

        // 2.减少循环 借助map方法
        // 循环 通过map 设值
        Map<String, List<People>> positionMap = new HashMap<>();
        for (People people : peoples) {
            String position = map.getOrDefault(people.getId(), "无");
            people.setPosition(position);
            //通过职位分组
            //computeIfAbsent 方法首先检查 position 是否已经在 positionMap 中存在。
            //如果 position 存在,它返回与该键关联的列表,并将 people 添加到该列表中。
            //如果 position 不存在,它执行计算函数(即 lambda 表达式 k -> new ArrayList<>()),创建一个新的空列表,并将其与 position 关联。
            //最后,它将 people 添加到新创建的列表中。
            positionMap.computeIfAbsent(position, k -> new ArrayList<>()).add(people);
        }
        //再放到一个对象中
        List<PositionObj> positionObjs = positionMap.entrySet().stream().map(entry -> new PositionObj(entry.getKey(), entry.getValue()))
                .collect(Collectors.toList());
        System.out.println(JSON.toJSON(positionObjs));

但是这个方法我之前是没怎么用到的,还有一些其他方法其实都不大熟悉,所以我想着不如来整理下Map的那些复杂的方法吧。

4.HashMap

4.1 getOrDefault 获取值或默认值

方法接收两个参数 getOrDefault(key,default),如果能通过key获取值则返回该value,如果不能,则返回默认值,这个常见与我们需要得到一些默认值,防止空值引发的空指针计算异常的情况。

示例如下

		Map<Integer, Integer> map = new HashMap<>();
		map.put(1, 1);
		System.out.println(map.getOrDefault(1, 0)); // 打印 1
		System.out.println(map.getOrDefault(2, 0)); // 打印 0

4.1 putIfAbsent 添加不存在的值

方法接收两个参数putIfAbsent(key, value),如果key已存在于map中,则不会覆盖,否则设值。

示例如下

		Map<Integer, Integer> map = new HashMap<>();
		map.putIfAbsent(1, 1); 
		map.putIfAbsent(1, 2); 
		System.out.println(map.get(1));// 打印为 1

4.2 replace 替换值

该方法有两个重载
replace(key, value),替换指定的key的value值,与put不同的是,如果该key不存在,则不进行替换。
replace(key, oldValue, newValue),替换指定key的value值,且需要比较老值是否相同,如果不相同,也不进行替换。

示例如下

replace(key, value)的情况

        Map<Integer, Integer> map = new HashMap<>();
        map.put(1, 1);
        System.out.println(map.replace(1, 2)); // 打印 1 说明返回了老的key
        System.out.println(map.replace(2, 2)); // 打印 null
        System.out.println(map.get(1)); // 打印 2

replace(key, oldValue, newValue)的情况

        Map<Integer, Integer> map = new HashMap<>();
        map.put(1, 1);
        System.out.println(map.replace(1, 2, 2)); // 打印 false 说明替换失败
        System.out.println(map.replace(1, 1, 2)); // 打印 true 说明替换成功
        System.out.println(map.get(1)); // 打印 2

4.3 replaceAll 替换全部

replaceAll(BiFunction),接收两个参数的lambda表达式,替换对应的value值。

        Map<Integer, Integer> map = new HashMap<>();
        map.put(1, 3);
        map.put(2, 2);
        map.put(3, 1);
        map.replaceAll((k, v) -> k * 10);
        System.out.println(map);// {1=10, 2=20, 3=30}
        map.replaceAll((k, v) -> v * 10);
        System.out.println(map);// {1=100, 2=200, 3=300}

我们可以看到,看似是k*10,其实最终改变的还是value值,和k值无关。

4.4 compute 计算

compute(key, BiFunction),接收两个参数,第一个参数是key,第二个参数是两个参数的lambda表达式,可以执行替换或者新增的作用,返回值计算好的value。

示例如下

        // 创建一个包含一些键值对的映射
        Map<String, Integer> map = new HashMap<>();
        map.put("A", 1);
        map.put("B", 2);

        // 使用 compute 方法对指定键进行重新映射
        String keyToCompute = "A";
        map.compute(keyToCompute, (key, oldValue) -> oldValue * 10); 

        // 输出更新后的映射
        System.out.println(map); // {A=10, B=2}
        // 使用 compute 方法添加新的键值对
        String newKeyToCompute = "C";
        map.compute(newKeyToCompute, (key, oldValue) -> 5);

        // 再次输出更新后的映射
        System.out.println(map); // {A=10, B=2, C=5}

我们可以看到,第一次起到了值替换的作用,第二次则可以添加不存在的kv值。

4.5 computeIfPresent 只针对存在k

computeIfPresent(key, BiFunction)
接收两个参数,第一个参数是key,第二个参数是两个参数的lambda表达式,它针对的是已存在的key值,进行计算,如果不存在某个key,则不会进行什么操作。

示例如下

        Map<String, Double> salaryMap = new HashMap<>();

        // 添加员工工资信息到映射
        salaryMap.put("Alice", 5000.0);
        salaryMap.put("Bob", 6000.0);
        salaryMap.put("Charlie", 7500.0);

        // 使用computeIfPresent给每个员工加薪10%
        salaryMap.computeIfPresent("Alice", (name, salary) -> salary * 1.10);
        salaryMap.computeIfPresent("Bob", (name, salary) -> salary * 1.10);
        salaryMap.computeIfPresent("Charlie", (name, salary) -> salary * 1.10);
        // 尝试给不存在的员工加薪,但不会执行操作
        salaryMap.computeIfPresent("David", (name, salary) -> salary * 1.10);

        // 打印加薪后的工资信息
        System.out.println("加薪后的工资信息: " + salaryMap); // 加薪后的工资信息: {Bob=6600.0, Alice=5500.0, Charlie=8250.0}

我们可以看到,David没有被加入到对应的map中。

4.6 computeIfAbsent 不存在k加默认值

computeIfAbsent(key, Function)
接收两个参数,第一个参数是key,第二个参数是一个参数的lambda表达式,它针对的是不存在的key值,一般设置默认值并进行计算,当然如果存在了,也会返回对应的v。

示例如下

因为这个我印象深刻,上面也用到了,所以在来一个例子加深一下。

public class ListMapExample {
    public static void main(String[] args) {
        // 创建一个映射,用于存储城市和其下属的商店列表
        Map<String, List<String>> cityStoreMap = new HashMap<>();

        // 向映射中添加商店信息
        addStore(cityStoreMap, "New York", "Store A");
        addStore(cityStoreMap, "New York", "Store B");
        addStore(cityStoreMap, "Los Angeles", "Store X");

        // 打印城市和商店列表
        for (Map.Entry<String, List<String>> entry : cityStoreMap.entrySet()) {
            String city = entry.getKey();
            List<String> stores = entry.getValue();

            System.out.println("城市: " + city);
            System.out.println("  商店列表: " + stores);
        }
        //城市: New York
        //  商店列表: [Store A, Store B]
        //城市: Los Angeles
        //  商店列表: [Store X]
    }

    // 使用computeIfAbsent方法添加商店信息到列表
    private static void addStore(Map<String, List<String>> cityStoreMap, String city, String store) {
        cityStoreMap
            .computeIfAbsent(city, k -> new ArrayList<>()) // 如果城市不存在,则创建一个新的列表
            .add(store); // 将商店添加到城市的商店列表中
    }
}

我们看addStore方法,它会将map中的城市作为k,商店列表作为v,而如果城市不存在,则创建列表,存在了,因为返回的是k对应的列表,所以正好能够添加到那个列表中去,起到了分组的作用。

4.7 merge 合并操作

merge(key, value,BiFunction)
接收三个参数,第一个参数是key,第二个参数是newValue,第三个参数是两个参数的的lambda表达式,但这里需要注意,使用的是当前k的oldValue和newValue,而不是k,v。

示例如下

public class WordFrequencyCounter {
    public static void main(String[] args) {
        String text = "This is a sample text It is a simple example";

        // 创建一个映射,用于存储单词和它们的出现次数
        Map<String, Integer> wordFrequencyMap = new HashMap<>();

        // 将文本拆分成单词并统计每个单词的出现次数
        String[] words = text.split("\\s+"); // 以空格分割文本
        for (String word : words) {
            // 使用merge方法更新单词的出现次数
            wordFrequencyMap.merge(word, 1, (v1,v2)->v1+v2);
        }
        System.out.println(wordFrequencyMap);// {a=2, This=1, simple=1, is=2, It=1, text=1, sample=1, example=1}
    }
}

分为两种情况讨论。
1.当k不存在,我们就使用newValue来填充。
2.当k存在,则通过oldValue和newValue进行求和,计算放入map。

有时间会再整理下ConcurrentHashMap,未完待续……

你怎么会没有选择,没有机会呢?时代在一直向前, 这种情况几乎不会发生。

文章来源:https://blog.csdn.net/weixin_44353507/article/details/135550634
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。