我什么时候应该在Java中使用IntStream.range?
我想知道何时可以有效地使用IntStream.range
。 我有三个原因,我不确定IntStream.range
是多么有用。
(请将开头和结尾视为整数。)
-
如果我想要一个数组,
[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()
非常慢。 -
我使用MersenneTwister来重排arrays。 (我在网上下载了MersenneTwister课程。)我认为没有办法使用MersenneTwister来
IntStream
。 -
我不认为从
start
到end-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
将为您提供一些通常应用于一系列整数的函数,例如sum
和avg
。 -
IntStream
允许您以一种function性的方式在一系列int上编写多个操作,这些操作可以更流畅地读取 – 特别是如果您有大量操作。
因此,当这些差异中的一个或多个对您有用时,基本上使用IntStream
。
但是请记住,改变Stream
声音非常奇怪,因为Stream
不是一个数据结构,因此将它洗牌并不是真的有意义(如果你打算建立一个特殊的IntSupplier
)。 改为改组结果。
至于性能,虽然可能会有一些开销,但在这两种情况下你仍然会迭代N次并且不应该关心更多。
您可以将Mersenne Twister实现为Iterator
并从中进行流式处理 。