我什么时候应该在Java中使用IntStream.range?

我想知道何时可以有效地使用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() IntStream.range(start, end).toArray()非常慢。

  2. 我使用MersenneTwister来重排arrays。 (我在网上下载了MersenneTwister课程。)我认为没有办法使用MersenneTwister来IntStream

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

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

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(); 

你说这是“非常慢”但是,和其他受访者一样,我怀疑你的基准测试方法。 对于小型数组,流设置确实存在更多开销,但这应该是如此之小以至于不可察觉。 对于大型arrays,开销应该可以忽略不计,因为填充大型arrays主要是内存带宽。

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

 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() 。 这应该为多核系统上的大arrays提供加速。

我发现在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循环则必须处理复制和resize,这会分散循环的核心逻辑。

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

这是一个例子:

 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的情况。

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

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

还有可读性参数:使用流的代码可以比循环更简洁,因此更具可读性,但我想展示一个示例,其中依赖于IntStrean的解决方案在function上也更优越。

我使用LongStream来强调这一点,但IntStream

是的,对于简单的求和,这可能看起来有点过分,但考虑例如水库采样

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

喜欢占据每个元素的正方形

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

这完全取决于用例。 但是,语法和流API增加了许多简单的一个衬里,它们肯定可以取代传统的循环。

在某些情况下, 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和传统循环在性能方面几乎相同

见:

  • Java 8嵌套循环,包含流和性能

基本上,如果您想要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

以下是IntStream.range和传统for循环之间的一些差异:

  • IntStream被懒惰地评估,在调用终端操作时遍历管道。 For循环在每次迭代时进行评估。
  • IntStream将为您提供一些通常应用于一系列整数的函数,例如sumavg
  • IntStream允许您以一种function性的方式在一系列int上编写多个操作,这些操作可以更流畅地读取 – 特别是如果您有大量操作。

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

但是请记住,改变Stream声音非常奇怪,因为Stream不是一个数据结构,因此将它洗牌并不是真的有意义(如果你打算建立一个特殊的IntSupplier )。 改为改组结果。

至于性能,虽然可能会有一些开销,但在这两种情况下你仍然会迭代N次并且不应该关心更多。

您可以将Mersenne Twister实现为Iterator并从中进行流式处理 。