如何使用 Java Streams API 添加和更新地图条目

Posted

技术标签:

【中文标题】如何使用 Java Streams API 添加和更新地图条目【英文标题】:How to add and update map entry using Java Streams API 【发布时间】:2021-12-19 04:24:37 【问题描述】:

我刚开始学习 Streams,我的任务是对某个字符串数组中的所有单词进行计数和排序。我已经将输入解析为单词,但我不知道如何使用流添加和更新条目。

有我的解析流:

Stream<String> stringStream = lines.stream().flatMap(s -> Arrays.stream(s.split("[^a-zA-Z]+")));
        String[] parsed =  stringStream.toArray(String[]::new);

我在没有流的情况下完成了这项任务,就像这样:

Map<String,WordStatistics> wordToFrequencyMap = new HashMap<>();
for (String line: lines) 
    line=line.toLowerCase();
    String[] mas =  line.split("[^a-zA-Z]+");
    for (String word:mas) 
        if(word.length()>3) 
            if (!wordToFrequencyMap.containsKey(word)) 
                wordToFrequencyMap.put(word, new WordStatistics(word, 1));
             else 
                WordStatistics tmp = wordToFrequencyMap.get(word);
                tmp.setFreq(tmp.getFreq() + 1);
            
        
    

WordStatistics 类:

public class WordStatistics implements Comparable<WordStatistics>
    private String word;
    private int freq;

    public WordStatistics(String word, int freq) 
        this.word = word;
        this.freq = freq;
    

    public String getWord() 
        return word;
    

    public int getFreq() 
        return freq;
    

    public void setWord(String word) 
        this.word = word;
    

    public void setFreq(int freq) 
        this.freq = freq;
    

    @Override
    public int compareTo(WordStatistics o) 
        if(this.freq > o.freq)
            return 1;
        if(this.freq == o.freq)
        
            return -this.word.compareTo(o.word);
        
        return -1;
    

【问题讨论】:

这个WordStatistics 类有什么好处? Map&lt;String,Integer&gt;Map&lt;String,Long&gt; 确实已经充分描述了词频。 @Holger 我还需要按频率对我的条目进行排序,然后在需要时按字母顺序(如果单词具有相同的频率),因此我决定将频率和单词存储在一起。我在compareTo 方法中有这个比较。 您无法对Map 进行排序,因此您的排序操作无论如何都必须将数据存储到一个新集合中,而这又不需要此类,例如lines.stream() .flatMap(Pattern.compile ( "[^a-zA-Z]+")::splitAsStream) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) .entrySet().stream() .sorted(Map.Entry.&lt;String,Long&gt;comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey())) .forEachOrdered(System.out::println); 不实现自定义 compareTo 方法可以防止您犯错误,例如使用减号(减号可以溢出)来反转。 【参考方案1】:

一种简单的方法是使用合并功能收集toMap()

Map<String, WordStatistics> wordToFrequencyMap = lines.stream()
        .map(s -> s.split("[^a-zA-Z]+"))
        .flatMap(Arrays::stream)
        .collect(Collectors.toMap(w -> w, w -> new WordStatistics(w, 1), (ws1, ws2) -> 
            ws1.setFreq(ws1.getFreq() + ws2.getFreq());
            return ws1;
        ));

分解toMap() 参数:

w -&gt; w 只是表示使用流元素作为映射键。 下一个参数为键生成一个值,该值最初是 WordStatistics 的新实例,频率为 1。 最后,我们告诉收集器如何将属于同一个键的值合并在一起。在我们的例子中,我们将频率相加为一个值 (ws1) 并将其作为合并结果返回。

【讨论】:

谢谢,它确实很好用!有一件事我想不通,你能澄清一下这段代码是如何工作的,尤其是 lambda 的那部分吗? .collect(Collectors.toMap(w -&gt; w, w -&gt; new WordStatistics(w, 1), (ws1, ws2) -&gt; ws1.setFreq(ws1.getFreq() + ws2.getFreq()); @Wtht.Nws 添加了一些解释。有关详细信息,请参阅链接的 Javadoc。【参考方案2】:

这应该与您现在在循环中所做的几乎相同。

Pattern pattern = Pattern.compile("[^a-zA-Z]+");
lines.stream().flatMap(pattern::splitAsStream).filter(s -> s.length() > 3).forEach(s -> 
    WordStatistics tmp = wordToFrequencyMap.get(s);
    if (tmp == null) 
        wordToFrequencyMap.put(s, new WordStatistics(word, 1));
     else 
        tmp.setFreq(tmp.getFreq() + 1);
    
);

【讨论】:

以上是关于如何使用 Java Streams API 添加和更新地图条目的主要内容,如果未能解决你的问题,请参考以下文章

java Streams API介绍

Java 8 中的 Streams API 详解

Java 8 中的 Streams API

Java 8 中的 Streams API 详解

如何(不)在Java 9+中使用Reactive Streams

Java Streams API的Javascript等价物