Java8 Stream超过了订单的集合一致性

Bis*_*der 4 collections lambda hashset java-8

据我所知,Set in java是一个无序集合,迭代器将按照其选择的某个顺序处理项目(我可能在这里错了),但确保它处理集合中的所有元素.

在Java8中,集合中的stream()API已经引入了跳过和限制功能.所以我想知道从流处理的项目的顺序是否保持相同,无论我开始流的次数或每次都是随机的?如果在流之间修改集合,订单会改变吗?

可能是无关紧要但我在这里提出问题:现在遇到问题,我有一套2000或者其他什么东西在创建后不会被修改,我正在进行50个批量操作,涉及每个批次的网络调用.我有一个启动参数,每次调用后增加50.如果我在我的Set上使用一个带有"start"的流作为每个批处理的skip参数,那么它对于每个批处理都是一个新流吗?因此,流程的顺序保持不变.显然,我不会多次相同的条目,更重要的是我不会错过任何条目.最简单的事情对我来说是一个Arraylist但我想知道我是否真的需要创建一个集合.

Eug*_*ene 9

让我们从这里的例子开始吧.首先,我认为这是显而易见的:

List<String> wordList = Arrays.asList("just", "a", "test");

    Set<String> wordSet = new HashSet<>(wordList);

    System.out.println(wordSet);

    for (int i = 0; i < 100; i++) {
        wordSet.add("" + i);
    }

    for (int i = 0; i < 100; i++) {
        wordSet.remove("" + i);
    }

    System.out.println(wordSet);
Run Code Online (Sandbox Code Playgroud)

输出将显示不同的"顺序" - 因为我们已经使容量更大(通过1-100添加)并且条目已经移动.他们仍然在那里 - 但是以不同的顺序(如果可以称为顺序).

所以,是的,一旦你修改Set了流之间的操作,"订单"可能会改变.

既然你说post创建Set将不会被修改 - 在当前的实现(无论是什么)下,订单目前保留.或者更确切地说,它不是内部随机化的 - 一旦条目被放入Set.

但这绝对不是一个人依赖的东西.事情可能会在没有通知的情况下发生变化,因为合同被允许这样做 - 文档并没有对任何订单做出任何保证 - Set毕竟这是关于唯一性的.

举个例子来说明jdk-9 Immutable Set并且Map 有一个内部随机化,"order"将从run到run改变:

Set<String> set = Set.of("just", "a", "test");
System.out.println(set);
Run Code Online (Sandbox Code Playgroud)

允许打印:

 [a, test, just] or [a, just, test]
Run Code Online (Sandbox Code Playgroud)

编辑

以下是随机化模式的样子:

/**
 * A "salt" value used for randomizing iteration order. This is initialized once
 * and stays constant for the lifetime of the JVM. It need not be truly random, but
 * it needs to vary sufficiently from one run to the next so that iteration order
 * will vary between JVM runs.
 */
static final int SALT;
static {
    long nt = System.nanoTime();
    SALT = (int)((nt >>> 32) ^ nt);
}
Run Code Online (Sandbox Code Playgroud)

这是做什么的:

花费很长的时间,将前32位与最后32位进行异或,从该长度取最后32位(通过强制转换为int).使用XOR是因为它具有50%的零和1分布,因此它不会改变结果.

如何使用(Set例如,对于两个元素):

// based on SALT set the elements in a particular iteration "order"
if (SALT >= 0) {
   this.e0 = e0;
   this.e1 = e1;
} else {
   this.e0 = e1;
   this.e1 = e0;
Run Code Online (Sandbox Code Playgroud)

我对jdk9内部随机化部分的猜测,最初取自这里,相关部分:

最后的安全功能是不可变Set元素和Map键的随机迭代顺序.HashSet和HashMap迭代顺序一直未指定,但相当稳定,导致代码无意中依赖于该顺序.当迭代顺序发生变化时,这会导致事情中断,这种情况偶尔会发生.新的Set/Map集合将其运行的迭代顺序更改为运行,希望在测试或开发早期清除顺序依赖性

所以它基本上是打破所有依赖于Set/的订单的代码Map.当人们从java-7迁移到java-8并且依赖于HashMap的顺序时,同样的事情发生了LinkedNode,由于TreeNodes的引入,这是不同的.如果你留下这样的功能并且人们依赖它多年 - 很难将它删除并执行一些优化 - 比如HashMap转移到TreeNodes; 因为现在你被迫保留这个命令,即使你不想这样做.但这显然只是猜测,请这样对待它

  • @Eugene你对随机化动机的猜测是正确的.我记得当时在core-libs dev邮件列表上阅读它.即使是OpenJDK测试代码也有很多这些依赖项,而且要解决这个问题还有一些工作要做.他们想避免再次落入同一个陷阱. (5认同)
  • 关于迭代顺序的很好的答案,但是一旦流是无序的,强调流上的`skip`和`limit`无论如何都没有绑定到迭代顺序也是很重要的. (4认同)
  • @FedericoPeraltaSchaffner我编辑了代码以展示它是如何实现的. (2认同)
  • @FedericoPeraltaSchaffner的'为什么'并不容易和老实说我只能想到更好地适应规范的情况(说不保证订单)...... (2认同)
  • @FedericoPeraltaSchaffner那里还有一段非常有趣的代码,*至少*对我来说.`ImmutableCollections.SetN` - 如何选择数组中的插槽... (2认同)

Hol*_*ger 5

这里有两个方面.正如尤金正确指出的那样,你不能假设一个HashSet迭代顺序保持不变 - 没有这样的保证.

但另一方面是StreamSpliterator不报告ORDERED特征时不需要维持迭代顺序的实现.

换句话说,如果流是无序的,skip(1)则不需要跳过第一个元素,因为没有"first"元素,而只是跳过一个元素.

虽然流不太可能实现随机化,但它们试图利用特征来最小化工作.一个看似合理的情况是,一个Stream实现将处理skip(n)一个无序但SIZED源,就像这样limit(size-n)也会有效地跳过n个元素,而工作量更少.

这样的优化可能不会在今天发生,但在下一个版本中,即使在HashSet迭代顺序没有改变的情况下也会破坏批处理场景.

  • 优点!我没有想到它,但它很有道理. (2认同)