【问题标题】:Convert values from map into set [closed]将地图中的值转换为集合[关闭]
【发布时间】:2025-12-05 07:15:02
【问题描述】:

我有一个字符串映射到字符串列表Map<String, List<String>>
这是从地图中的值创建 Set 的最有效方法吗?

for (List<String> localList : map.values()) {
    set.addAll(localList);
}

【问题讨论】:

  • 使用分析器测量代码中的瓶颈。这比询问 SO 或自己猜测效率要准确得多。
  • 您可能会使用流编写一个单行,但我看不出有任何方法可以迭代您的列表集合并将每个列表添加到总集合中。
  • @TimBiegeleisen 也许是这个? Set&lt;String&gt; set = map.values().stream().flatMap(List::stream).collect(Collectors.toSet()); 虽然如果不深入研究我怀疑的来源,可能很难量化效率的提高。
  • @Mena 你太棒了,兄弟,我敢打赌,这可能会略胜于 OP 目前的表现。
  • 为什么需要高效的方式?

标签: java collections hashset


【解决方案1】:

取决于“最有效的方式”是什么意思。

从性能的角度来看,您的代码是最高效的。

使用 Java 8 Streams 可以简化代码,但性能会有所下降,但在总体方案中不太可能引起注意,因此 Streams 可能“更高效”。

然而,这主要是一个意见问题。

为了比较,您的代码:

Set<String> set = new HashSet<>();
for (List<String> localList : map.values()) {
    set.addAll(localList);
}

与 Stream 版本相比:

Set<String> set = map.values().stream().flatMap(List::stream).collect(Collectors.toSet());

为了可读性可以写成:

Set<String> set = map.values()
                     .stream()
                     .flatMap(List::stream)
                     .collect(Collectors.toSet());

如果要控制Set的类型,把最后一部分改成:

                     .collect(Collectors.toCollection(TreeSet::new));

【讨论】:

  • 对于流媒体版本,如果在 .stream() 之后添加 .parallel() 可能会更快
  • @xyz 不太可能。如果Set 不是并发的,那么所有值最终都必须添加到单个Set 中,所以多线程是一种浪费。如果Set 是并发的,从技术上讲,所有值都可以在多个线程中添加到最终的Set,但是即使使用多个线程的开销,以及插入时冲突的降级,都将超过性能的提升,除非你必须处理 大量 值。
【解决方案2】:

Set接口处,任何周边代码都一样

... other code ...
mySet.add(item);
... other code ...

性能影响来自您在调用中放置的用于将项目添加到集合中的循环类型。 (好吧,Set 的类型也很重要,但这是最容易改变的)。

一般Iterators慢一点,索引快一点;但这是一个概括。 “foreach”循环倾向于使用自动生成的迭代器方法,至少到 Java8。我没有调查过去的版本。具体细节,需要进行基准测试。

一般来说,流中使用的隐藏对象(收集器、生产者等)的额外开销也会导致更多的对象被处理,其中一些对象是处理环境的管道框架,推动项目通过流。同样,在我的基准测试(直到 Java8)中,这产生了显着影响,这主要是通过使用传统 C 风格 for 循环方法的直接索引来避免的。

现在要注意了

我只使用ArrayLists,因此我的性能在按索引拉出项目时会更快,这是完全合理的。我不知道您的列表中的具体类是什么。

另外,我没有将我的列表存储在 Map 中,所以在某种程度上,这在某种程度上取决于您用于地图的内容。

建议

要做出的选择太多,每个选择在一种或另一种情况下效果更好,才能真正事先了解这类信息。我强烈建议对此类列表的典型(但不同)地图进行一组基线测量,然后尝试重新运行您的基准测试的小改动。这是真正了解优化环境中性能影响的唯一方法。其他一切都只是猜测。

【讨论】:

  • 我将忽略那些认为“最有效”是在源代码文件中输入最少字符数的人。生产中的这种代码高尔夫有时会导致效率非常低,并且会重视个人时间而忽略其产品的运行时特性。
【解决方案3】:

我假设您将性能作为最有效方式的关键。在这种情况下,简单的基准测试可以提供最佳答案。这是一个简单的 JMH 基准测试:

package test;

import org.openjdk.jmh.annotations.*;

import java.util.*;
import java.util.stream.Collectors;

public class ListOfStringsToSetBenchmark {

    public static void main(String[] args) throws Exception {
        org.openjdk.jmh.Main.main(args);
    }

    @Benchmark
    @Fork(value = 1, warmups = 1)
    @Measurement(iterations = 5)
    @Warmup(iterations = 3)
    @BenchmarkMode(Mode.Throughput)
    public void streams() {
        Map<String, List<String>> map = new HashMap<>();

        map.put("test", Arrays.asList("1", "2", "3"));
        map.put("test2", Arrays.asList("4", "5", "6"));
        map.put("test3", Arrays.asList("7", "8", "9"));

        Set<String> collect = map.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
    }

    @Benchmark
    @Fork(value = 1, warmups = 1)
    @Measurement(iterations = 5)
    @Warmup(iterations = 3)
    @BenchmarkMode(Mode.Throughput)
    public void parallelStreams() {
        Map<String, List<String>> map = new HashMap<>();

        map.put("test", Arrays.asList("1", "2", "3"));
        map.put("test2", Arrays.asList("4", "5", "6"));
        map.put("test3", Arrays.asList("7", "8", "9"));

        Set<String> collect = map.values().parallelStream().flatMap(Collection::parallelStream).collect(Collectors.toSet());
    }

    @Benchmark
    @Fork(value = 1, warmups = 1)
    @Measurement(iterations = 5)
    @Warmup(iterations = 3)
    @BenchmarkMode(Mode.Throughput)
    public void forEach() {
        Map<String, List<String>> map = new HashMap<>();

        map.put("test", Arrays.asList("1", "2", "3"));
        map.put("test2", Arrays.asList("4", "5", "6"));
        map.put("test3", Arrays.asList("7", "8", "9"));

        Set<String> set = new HashSet<>();

        for (List<String> localList : map.values()) {
            set.addAll(localList);
        }
    }
}

结果

Benchmark                              Mode  Cnt        Score        Error  Units
ListOfStringsToSetBenchmark.forEach          thrpt    5  5290023,805 ±  89846,320  ops/s
ListOfStringsToSetBenchmark.parallelStreams  thrpt    5   588714,960 ±   6289,819  ops/s
ListOfStringsToSetBenchmark.streams          thrpt    5  2940686,522 ± 359335,288  ops/s

注意:为了获得精确的结果,您需要增加迭代值。

【讨论】:

  • 您也可以尝试使用具有CONCURRENT 特征的ConcurrentSkipListSet 收集器的并行版本。
最近更新 更多