当前位置: 首页 > 知识库问答 >
问题:

Java 8 findFirst和相遇顺序

胡鸿羲
2023-03-14

findFirst的JavaDocs说,如果流有遇到顺序,那么第一个元素将始终返回,但如果流没有遇到顺序,则可以返回任何元素。

我试图演示这如何在没有遭遇顺序的流上工作,但是除了实际的第一个元素之外,我不能让它返回任何东西。

我尝试将元素添加到< code >集合中,该集合没有已定义的相遇顺序:

    Set<String> words = new HashSet<>();
    words.addAll(Arrays.asList("this", "is", "a", "stream", "of", "strings"));
    Optional<String> firstString = words.stream()
            .findFirst();
    System.out.println(firstString);

每次我跑步时,我都会得到一个作为第一个字符串。然后,我尝试在将列表添加到集合之前在列表上进行 Collections.shuffle,但这并没有改变任何内容。

    List<String> wordList = Arrays.asList("this", "is", "a", "stream", "of", "strings");
    words = new HashSet<>();
    words.addAll(wordList);
    firstString = words.stream()
            .findFirst();
    System.out.println(firstString);

我还是每次都回单词< code>a。

然后,我尝试使用BaseStream中的无序方法,该方法声称返回没有遇到顺序的流,但没有区别:

    firstString = Stream.of("this", "is", "a", "stream", "of", "strings")
            .unordered()
            .findFirst();
    System.out.println(firstString);

现在我每次都能听到< code>this这个词。我错过了什么吗?有没有办法证明无序流上的< code>findFirst返回不同的值?

共有3个答案

阳凌
2023-03-14

通过将您的流标记为无序的,您实际上并没有使它成为无序的(您并没有使您的集合中的顺序有任何不同),而是移除了有序流可能施加的任何限制。

证明这将返回不同结果的方法是使用并行流。

 Set<String> words = new HashSet<>();
    words.addAll(Arrays.asList("this", "is", "a", "stream", "of", "strings"));
    Optional<String> firstString = words.stream().parallel()
            .findFirst();
    System.out.println(firstString);

运行几次,显示:

  Optional[strings] and then Optional[this]

将Set更改为List并并行运行将保留顺序:

 List<String> words = new ArrayList<>();
    words.addAll(Arrays.asList("this", "is", "a", "stream", "of", "strings"));
    Optional<String> firstString = words.stream().parallel()
            .findFirst();
    System.out.println(firstString); // always Optional[this]

这里绝对必读的是霍尔格伟大的回答

宗弘扬
2023-03-14

Holger已经巧妙地解释了这种情况。(1)我想提供一个HashSet实例的演示,这些实例具有相同的内容但具有不同的迭代顺序。首先,我们像以前一样创建一个集合:

    List<String> wordList = Arrays.asList("this", "is", "a", "stream", "of", "strings");
    Set<String> words = new HashSet<>(wordList);

我们创建另一组单词,添加一堆东西(具体是什么并不重要),然后删除它:

    Set<String> words2 = new HashSet<>(wordList);
    IntStream.range(0, 50).forEachOrdered(i -> words2.add(String.valueOf(i)));
    words2.retainAll(wordList);

如果我们检查结果如下:

    System.out.println(words.equals(words2));
    System.out.println(words);
    System.out.println(words2);

我们从输出中可以看出,集合是相等的,但以不同的顺序迭代

true
[a, strings, stream, of, this, is]
[this, is, strings, stream, of, a]

如前所述,如果您从这些集合中获取一个流并调用< code>findFirst(),结果是迭代顺序中的第一个元素,这在这些集合之间会有明显的不同。

发生的情况是,通过添加和删除一堆元素,我们已经导致集合增加了其内部表的大小,需要对元素进行重新散列。原始元素在新表中以不同的相对位置结束,即使新元素已被删除。

尽管< code > hashset 没有指定的迭代顺序,但是如果每次都用相同的内容以相同的方式初始化集合,那么这个顺序可能是可重复的(甚至是可预测的)。因此,我们说集合中的流没有定义的相遇顺序,即使顺序每次都是相同的。

请注意,在JDK 9中,新的不可变集(和映射)实际上是随机化的,因此它们的迭代顺序会随着运行而变化,即使它们每次都以相同的方式初始化。

卢涵畅
2023-03-14

“任何”都包括“第一”的可能性。当然,流实现不会在随机化数据方面浪费精力,因此对于许多情况,特别是顺序执行,如果我们可以这样称呼它,它仍然是第一个元素(因为没有顺序,就没有可区分的第一个元素)。

findFirst 中展示不同结果的最佳机会是并行流。但即使在那里,也不是每个操作组合都适合表现出无序性。

一点是,在当前实现中,findFirst()操作在流无序时不会改变其行为,即它不会主动尝试像findAny()那样。由于流的来源,它可能仍然表现出不可预测的行为,但如果源代码是流。of(“this”、“is”、“a”、“stream”、“of”、“strings”),即一个已知大小的不可变序列,它已经具有尽可能好的并行性能,因此根本无法从链式<code>无序()中获益,因此,当前的实现不会改变其行为。

这可能会让人惊讶,但在某种程度上,这甚至适用于<code>哈希集。虽然它有一个未指定的顺序,但在某个时间点,它的支持数组中会有一个实际的顺序,只要你不修改<code>集</code>就没有理由乱洗这些条目,因此对于一个特定的<code>哈希集</code>实例,你可能会重复得到相同的“first”元素,尽管它没有指定哪一个,甚至在一个运行时内,另一个表示相同内容但具有不同历史的<code>哈希集</code>实例可能具有不同的顺序。

已知可从无序特性中获益的操作的一个例子是distinct。虽然它必须对重复项进行排序,但如果有显著差异,它必须保持第一次遇到的元素相等。这会显著降低性能,因此,如果流无序,实现将立即尝试获得好处。例如。

List<String> equal=IntStream.range(0, 100)
    .mapToObj(i->new String("test")) // don't do this in normal code
    .collect(Collectors.toList());
Map<String, Integer> map = IntStream.range(0, equal.size())
    .collect(IdentityHashMap::new, (m,i)->m.put(equal.get(i),i), Map::putAll);

equal.parallelStream().distinct().map(map::get)
     .findFirst().ifPresent(System.out::println);

这将创建一堆相等但可区分的 String 实例(您通常不应该这样做),在 IdentityHashMap 中用它们的位置编号注册它们,以便我们可以找出 distinct 保留的实例。由于上面的代码使用由 List 创建的有序流,因此无论您执行频率如何,它始终打印 0

相比之下,

equal.parallelStream().unordered().distinct().map(map::get)
     .findFirst().ifPresent(System.out::println);

将打印范围的任意数字,因为我们已经发布了有序合约并允许选择任何相等的字符串

如前所述,这都是特定于实现的。您永远不应该假设某个操作是否实际上可以获得好处,从而改变其对无序流的行为。上面的解释只是为了说明为什么有时特定实现的行为可能不会在无序流中改变。不过,它仍然可能在下一个版本或不同的JRE实现中。

 类似资料:
  • 的Javadoc表示(强调是我的): 此操作的行为显式不确定。对于并行流管道,此操作不能保证尊重流的相遇顺序,因为这样做会牺牲并行性的好处。对于任何给定的元素,操作可以在库选择的任何时间和线程中执行。如果操作访问共享状态,则它负责提供所需的同步。 同样的文本也出现在Java9早期访问Javadoc中。 如果forEach不保留遭遇顺序,则会引入bug。在报告针对NetBeans的bug之前,我想知

  • 问题内容: 在对JavaDoc中findFirst说,如果流有一个邂逅的命令,那么第一个元素总是会返回,但如果流没有遭遇订单,可以返回的任何元素。 我试图证明它在没有遇到顺序的情况下如何在流中工作,但是我无法让它返回除实际第一个元素以外的任何东西。 我尝试将元素添加到中Set,该元素没有定义的遇到顺序: 每次运行时,我都会得到a第一个字符串。然后,我尝试Collections.shuffle对进行

  • 问题内容: Javadoc 表示(重点是我): 该操作的行为明确地是不确定的。 对于并行流管道,此操作不能保证遵守流的遇到顺序 ,因为这样做会牺牲并行性的好处。对于任何给定的元素,可以在库选择的任何时间和线程中执行操作。如果操作访问共享状态,则它负责提供所需的同步。 Java 9 Early Access Javadoc中提供了相同的文本。 第一句话(“明确地不确定”)表明(但未明确说明)此方法未

  • 对于以下地图: 这是代码。。。 ...保证两次打印相同的序列? 如果没有,在例如< code>java.util.HashMap中是否有任何保证?

  • 由于计算,海量数据存储和互联网技术等关键领域的共同发展,机器学习领域呈现了巨大的发展。许多人的日常生活中的许多技术和事件,直接或间接地受到自动学习的影响。

  • 是否有任何保证在顺序和有序流上的操作是按遇到顺序处理的? 我是说,如果我有这样的代码: 是否可以保证它将按照生成范围的遇到顺序执行myFunction()调用? 我找到了Stream类的JavaDocs草案,它明确地说明了以下内容: 对于顺序流管道,如果管道源具有已定义的遇到顺序,则所有操作都按照管道源的遇到顺序执行。 但是它没有提到顺序流,这个例子是针对并行流的(我的理解是,顺序流和并行流都是正