java刷题技巧之复杂map的使用技巧

2023-07-24 18:23:48 浏览数 (1)

首先想一想当你在构建复杂 map Map<K,Collection<V>> 时你是怎么做的?

一般场景是我们拿到一个 List 对其进行遍历分组操作。假如我们有一个学生成绩列表,现在需要根据得分进行分组,查找每个得分是哪些人。

用于演示的数据

代码语言:javascript复制
/**
 * 学生信息类
 *
 * @author 一个程序猿的异常
 * @date 2021/09/17
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student implements Serializable {
 private Integer id;
 private String name;
 private Integer age;
 private Integer score;
}

Java8之前的做法

  1. 初始化map
  2. 根据key获取value
  3. 判断value是否为空,为空初始化集合
  4. 将当前值存入集合
  5. 当前key&集合存入map

代码如下:

代码语言:javascript复制
@Test
public void testSample() {
    List<Student> students = new ArrayList<>();
    students.add(new Student(1, "威震天", 200, 100));
    students.add(new Student(2, "灭霸", 201, 90));
    students.add(new Student(3, "奥特曼", 2000, 100));
    students.add(new Student(4, "哥斯拉", 200, 80));

    Map<Integer, List<Student>> scoreMap = new HashMap<>(16);

    for (int i = 0; i < students.size(); i  ) {
        Student student = students.get(i);
        Integer score = student.getScore();
        List<Student> scoreList = scoreMap.get(score);
        if (scoreList == null) {
            scoreList = new ArrayList<>();

        }
        scoreList.add(student);
        scoreMap.put(score, scoreList);
    }

    for (Map.Entry<Integer, List<Student>> entry : scoreMap.entrySet()) {
        Integer score = entry.getKey();
        List<Student> entryValue = entry.getValue();
        System.out.println(String.format("分数为%s的学生是:", score));
        System.out.println(String.format("----%s", entryValue));
    }

}

java8实现

使用 Stream 进行操作

关键代码如下:

代码语言:javascript复制
Map<Integer, List<Student>> scoreMap = students.stream().collect(Collectors.groupingBy(Student::getScore));

一行搞定,但是大多数情况下并非如此简单,需要在遍历过程进行其他操作,接下来引出本文的重点。

Map#computeIfAbsent

computeIfAbsent() 方法对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hashMap 中。

computeIfAbsent() 方法的语法为:

代码语言:javascript复制
hashmap.computeIfAbsent(K key, Function remappingFunction)

还是使用上面学生分数分组的例子进行演示,关键代码如下:

代码语言:javascript复制
for (int i = 0; i < students.size(); i  ) {
    Student student = students.get(i);
    Integer score = student.getScore();
    List<Student> list = scoreMap.computeIfAbsent(score, s -> new ArrayList<>());
    list.add(student);
}

对比java7的版本:

代码语言:javascript复制
for (int i = 0; i < students.size(); i  ) {
    Student student = students.get(i);
    Integer score = student.getScore();
    List<Student> scoreList = scoreMap.get(score);
    if (scoreList == null) {
        scoreList = new ArrayList<>();
    }
    scoreList.add(student);
    scoreMap.put(score, scoreList);
}

6行代码简化成了2行!!!

到这就完了吗?

如果你使用过 googleguava 库的话对 Multimap 一定不陌生。

Guava工具库Multimap实现

依旧使用按照学生分数进行分组的例子进行演示,关键代码如下:

代码语言:javascript复制
ListMultimap<Integer, Student> scoreMap = ArrayListMultimap.create();
for (int i = 0; i < students.size(); i  ) {
    Student student = students.get(i);
    Integer score = student.getScore();
    scoreMap.put(score, student);
}
for (Integer score : scoreMap.keySet()) {
    List<Student> entryValue = scoreMap.get(score);
    System.out.println(String.format("分数为%s的学生是:", score));
    System.out.println(String.format("----%s", entryValue));
}

当你在构建比较复杂的 Map<K,Collection<V>> 时使用 Multimap 再合适不过了。他的应用场景是当你需要构造像 Map<K, List<V>> 或者 Map<K, Set<V>> 这样比较复杂的集合类型的数据结构,来做相应的业务逻辑处理。它会把相同key和value的值给覆盖起来,但是相同的key又可以保留不同的value。

Multimap的实现类

Multimap提供了丰富的实现,所以你可以用它来替代程序里的 Map<K, Collection<V>> 具体的实现如下:

Implementation

Keys 的行为类似

Values的行为类似

ArrayListMultimap

HashMap

ArrayList

HashMultimap

HashMap

HashSet

LinkedListMultimap

LinkedHashMap*

LinkedList*

LinkedHashMultimap

LinkedHashMap

LinkedHashSet

TreeMultimap

TreeMap

TreeSet

ImmutableListMultimap

ImmutableMap

ImmutableList

ImmutableSetMultimap

ImmutableMap

ImmutableSet

以上这些实现,除了immutable的实现都支持null的键和值。

  1. LinkedListMultimap.entries()能维持迭代时的顺序。
  2. LinkedHashMultimap维持插入的顺序,以及键的插入顺序。 要注意并不是所有的实现都正真实现了 Map<K, Collection<V>> (尤其是有些Multimap的实现为了最小话开销,使用了自定义的hash table)

0 人点赞