【问题标题】:When should I use IntStream.range in Java?什么时候应该在 Java 中使用 IntStream.range?
【发布时间】:2016-12-24 05:20:29
【问题描述】:

我想知道什么时候可以有效地使用IntStream.range。我不确定IntStream.range 有多大用处的三个原因。

(请将开始和结束视为整数。)

  1. 如果我想要一个数组,[start, start+1, ..., end-2, end-1],下面的代码要快得多。

    int[] arr = new int[end - start];
    int index = 0;
    for(int i = start; i < end; i++)
        arr[index++] = i;
    

    这可能是因为IntStream.range(start, end).toArray() 中的toArray() 非常慢。

  2. 我使用 MersenneTwister 对数组进行洗牌。 (我在网上下载了 MersenneTwister 课程。)我认为没有办法使用 MersenneTwister 随机播放 IntStream

  3. 我不认为仅仅从startend-1 获取int 数字是有用的。我可以使用for(int i = start; i &lt; end; i++),这看起来更容易而且不慢。

你能告诉我什么时候应该选择IntStream.range吗?

【问题讨论】:

  • 您可以使用IntStream.range() 将结果流作为参数传递给不同的方法。 for 无法做到这一点。
  • 一个面试问题最好通过 IntStream.range 来解决:Array list algorithm - Interview
  • 这可能是因为 toArray() [...] 非常慢。。你是如何测量这个的?您可以发布或链接到有效的基准吗? “非常慢”是什么意思?
  • @Nickel 您的基准很可能存在缺陷。衡量 Java 性能不像比较两个时间戳。
  • @Nickel:您的 range2 方法没有任何作用。当然,将值写入数组比什么都不做需要更多的时间,但是与您声称 toArrayfor 循环执行相同操作慢的相关性在哪里?

标签: java java-8 java-stream


【解决方案1】:

IntStream.range 有多种用途。

一种是自己使用int 值:

IntStream.range(start, end).filter(i -> isPrime(i))....

另一种是做某事N次:

IntStream.range(0, N).forEach(this::doSomething);

您的情况(1)是创建一个填充范围的数组:

int[] arr = IntStream.range(start, end).toArray();

您说这“非常慢”,但与其他受访者一样,我怀疑您的基准测试方法。对于小型阵列,流设置确实有更多开销,但这应该小到不明显。对于大型数组,开销应该可以忽略不计,因为填充大型数组主要取决于内存带宽。

有时您需要填充现有数组。你可以这样做:

int[] arr = new int[end - start];
IntStream.range(0, end - start).forEach(i -> arr[i] = i + start);

有一个实用方法Arrays.setAll 可以更简洁地做到这一点:

int[] arr = new int[end - start];
Arrays.setAll(arr, i -> i + start);

还有Arrays.parallelSetAll 可以并行填充现有数组。在内部,它只是使用IntStream 并在其上调用parallel()。这应该为多核系统上的大型阵列提供加速。

我发现我在 Stack Overflow 上的很多回答都涉及使用 IntStream.range。您可以使用搜索框中的这些搜索条件来搜索它们:

user:1441122 IntStream.range

IntStream.range 我发现一个特别有用的应用是对数组的元素进行操作,其中数组索引以及数组的值都参与了计算。有一大堆这样的问题。

例如,假设您想在一个数组中找到不断增加的数字运行的位置。结果是第一个数组的索引数组,其中每个索引都指向运行的开始。

要计算此值,请观察运行从值小于前一个值的位置开始。 (运行也从位置 0 开始)。因此:

    int[] arr = { 1, 3, 5, 7, 9, 2, 4, 6, 3, 5, 0 };
    int[] runs = IntStream.range(0, arr.length)
                          .filter(i -> i == 0 || arr[i-1] > arr[i])
                          .toArray();
    System.out.println(Arrays.toString(runs));

    [0, 5, 8, 10]

当然,您可以使用 for 循环执行此操作,但我发现在许多情况下使用 IntStream 更可取。例如,使用toArray() 很容易将未知数量的结果存储到数组中,而使用 for 循环则必须处理复制和调整大小,这会分散循环的核心逻辑。

最后,并行运行IntStream.range 计算要容易得多。

【讨论】:

  • 为什么在第三个示例中使用forEach 写入预先分配的数组而不是使用干净的int[] arr = IntStream.range(start, end).toArray();
  • @Holger 有时你想填充一个预先存在的数组。 OP声称IntStream.range(start, end).toArray()太慢了,所以他清楚地知道这一点。但无论如何我应该澄清上下文。另外,只有Arrays.parallelSetAll 使用IntStream,所以我也会对其进行调整。
  • @Holger 谢谢你的建议。抱歉,我真的很困惑,并且在我的基准测试中犯了错误。但是,在我的笔记本电脑(Core i7 4710MQ,Java8u92)上确实如此,使用预制数组比使用 toArray() 更快
  • int[] arr = new int[end - start]; // Arrays.setAll(i -&gt; i + start); 看起来不正确 - 缺少 Arrays.setAll() 的参数?
  • @MyStackRunnethOver 是的,缺少 arg,谢谢。固定。
【解决方案2】:

IntStream.range 将整数范围作为流返回,因此您可以对其进行流处理。

喜欢对每个元素取平方

IntStream.range(1, 10).map(i -> i * i);  

【讨论】:

【解决方案3】:

这是一个例子:

public class Test {

    public static void main(String[] args) {
        System.out.println(sum(LongStream.of(40,2))); // call A
        System.out.println(sum(LongStream.range(1,100_000_000))); //call B
    }

    public static long sum(LongStream in) {
        return in.sum();
    }

}

那么,让我们看看sum() 做了什么:它计算任意数字流的总和。我们以两种不同的方式调用它:一种是明确的数字列表,另一种是范围。

如果您只有call A,您可能很想将这两个数字放入一个数组中并将其传递给sum(),但这显然不是call B 的选项(您会耗尽内存)。同样,你可以只传递call B 的开始和结束,但你不能支持call A 的情况。

总而言之,范围在这里很有用,因为:

  • 我们需要在方法之间传递它们
  • 目标方法不仅适用于范围,还适用于任何数字流
  • 但它只对流的单个数字进行操作,按顺序读取它们。 (这就是为什么使用流改组通常是一个糟糕的主意。)

还有可读性论点:使用流的代码比循环更简洁,因此更具可读性,但我想展示一个示例,其中依赖于 IntStreans 的解决方案在功能上也更出色。

我使用LongStream 来强调这一点,但IntStream 也是如此

是的,对于简单的求和,这可能看起来有点矫枉过正,但请考虑例如reservoir sampling

【讨论】:

  • 说到可读性,我宁愿写 100_000_000,但那是我 :-)
  • 很公平,我只是捣碎了很多零而不计算:)
  • 谢谢!水库取样就是一个很好的例子!
【解决方案4】:

基本上,如果你想要Stream 操作,你可以使用range() 方法。例如,要使用并发或想使用map()reduce()。那你最好用IntStream

例如:

IntStream.range(1, 5).parallel().forEach(i -> heavyOperation());

或者:

IntStream.range(1, 5).reduce(1, (x, y) -> x * y)  
// > 24

您也可以使用 for 循环来实现第二个示例,但您需要中间变量等。

另外,例如,如果您想要第一场比赛,您可以使用findFirst() 和堂兄弟来停止消费Stream 的其余部分

【讨论】:

  • better 在我看来不太合适。
  • @Jean-FrançoisSavard 请解释一下您的意见?
  • 第二个等价于for(int i = 1; i &lt; 5; i++) result *= i;。两者是不同的,但不是更好。中间变量并不是一个真正的问题(如果你做的一切都是乘法,那么无论如何使用流方法都会有很多开销)。第一个似乎缺乏一点解释。当然并行可能是一个好方法,但在你的例子中,我宁愿创建一个执行heavyOperation的5个线程的线程池,i实际上并没有在例子中使用。
  • 为了清楚起见——我并不是在争论传统循环是好是坏。两者都一样好,但如果我知道我可能在循环中执行很多操作,以便我的代码更流畅地阅读(以功能方式),我宁愿使用IntStream
【解决方案5】:

这完全取决于用例。但是,语法和流 API 增加了很多简单的 1 行,绝对可以替代传统的循环。

IntStream 在某些情况下确实很有帮助,语法糖

IntStream.range(1, 101).sum();
IntStream.range(1, 101).average();
IntStream.range(1, 101).filter(i -> i % 2 == 0).count();
//... and so on

无论您可以使用IntStream 做什么,您都可以使用常规循环。因为一个班轮更易于理解和维护。

对于负循环,我们不能使用IntStream#range,它只能以正增量工作。所以跟随是不可能的,

for(int i = 100; i > 1; i--) {
    // Negative loop
}
  • 案例 1: 是的,在这种情况下,传统循环要快得多,因为 toArray 有一点开销。

  • 案例 2:我对此一无所知,抱歉。

  • 案例 3:IntStream 一点也不慢,IntStream.range 和常规循环在性能方面几乎相同

见:

【讨论】:

  • 或者,也可以IntStream.iterate(from - 1, i -&gt; i - 1).limit(from - to)
  • IntStream 与传统的 for 循环一样快(除非调用 parallel()),但它更节省内存并且需要更短的代码。对吗?
  • @Nickel:对于大多数用例,连续的IntStream 将与传统循环一样快,尽管它取决于很多因素。最好说数量级是相同的,细微的、不可预测的差异是无关紧要的。它往往需要 更多 内存,但这是一个 临时 内存使用,甚至可能会被忽视。最重要的区别是,当使用IntStream 时,您确实可以用更简单的代码表达很多任务。
  • @Jean-François Savard:不要低估 HotSpot 优化器。应用于int%2 很容易识别。而且由于它的实现方式,range(…).filter(…) 可能与.iterate(…).limit(…) 相当甚至更有效。
【解决方案6】:

我想到了 IntStream.range 和传统 for 循环之间的一些区别:

  • IntStream 被延迟评估,在调用 terminal 操作时遍历管道。 For 循环在每次迭代时求值。
  • IntStream 将为您提供一些通常应用于一系列 int 的函数,例如 sumavg
  • IntStream 将允许您以功能性的方式对 int 范围内的多个操作进行编码,这样可以更流畅地阅读 - 特别是如果您有很多操作。

因此,当这些差异中的一个或多个对您有用时,基本上使用IntStream

但请记住,将Stream 改组听起来很奇怪,因为Stream 不是数据结构,因此改组它实际上没有意义(如果您计划构建一个特殊的@987654330 @)。而是随机播放结果。

至于性能,虽然可能会有一些开销,但在这两种情况下您仍然会迭代 N 次,不必太在意。

【讨论】:

    【解决方案7】:

    您可以将 Mersenne Twister 实现为 Iteratorstream from that

    【讨论】:

    • 我在想类似的事情,但将 MT 实现为IntSupplier,然后是IntStream#generate
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 2011-04-15
    • 2017-04-10
    • 2012-03-19
    • 2018-05-12
    • 2018-12-11
    • 1970-01-01
    相关资源
    最近更新 更多