什么时候应该在 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;

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

  1. 我使用 MersenneTwister 来打乱数组。 (我在线下载了 MersenneTwister 课程。)我认为没有办法使用 MersenneTwister 洗牌 IntStream

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

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

原文由 user5790923 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 944
2 个回答

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 计算要容易得多。

原文由 Stuart Marks 发布,翻译遵循 CC BY-SA 3.0 许可协议

这是一个例子:

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

综上所述,范围在这里很有用,因为:

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

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

我用 LongStream 来强调这一点,但同样适用于 IntStream

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

原文由 biziclop 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题