6.7 并行化数组操作
Java 8还引入了一些针对数组的并行操作,脱离流框架也可以使用Lambda表达式。像流框架上的操作一样,这些操作也都是针对数据的并行化操作。让我们看看如何使用这些操作解决那些使用流框架难以解决的问题。
这些操作都在工具类Arrays中,该类还包括Java以前版本中提供的和数组相关的有用方法,表6-1总结了新增的并行化操作。
表6-1:数组上的并行化操作
| 方法名 | 操作 |
|---|---|
parallelPrefix
| 任意给定一个函数,计算数组的和 |
parallelSetAll
| 使用Lambda表达式更新数组元素 |
parallelSort
| 并行化对数组元素排序 |
读者可能以前写过类似例6-7的代码,使用一个for循环初始化数组。在这里,我们用数组下标初始化数组中的每个元素。
例6-7 使用for循环初始化数组
public static double[] imperativeInitilize(int size) {double[] values = new double[size];for(int i = 0; i < values.length;i++) {values[i] = i;}return values;}
使用parallelSetAll方法能轻松地并行化该过程,代码如例6-8所示。首先提供了一个用于操作的数组,然后传入一个Lambda表达式,根据数组下标计算元素的值。在该例中,数组下标和元素的值是一样的。使用这些方法有一点要小心:它们改变了传入的数组,而没有创建一个新的数组。
例6-8 使用并行化数组操作初始化数组
public static double[] parallelInitialize(int size) {double[] values = new double[size];Arrays.parallelSetAll(values, i -> i);return values;}
parallelPrefix操作擅长对时间序列数据做累加,它会更新一个数组,将每一个元素替换为当前元素和其前驱元素的和,这里的“和”是一个宽泛的概念,它不必是加法,可以是任意一个BinaryOperator。
使用该方法能计算的例子之一是一个简单的滑动平均数。在时间序列上增加一个滑动窗口,计算出窗口中的平均值。如果输入数据为0、1、2、3、4、3.5,滑动窗口的大小为3,则简单滑动平均数为1、2、3、3.5。例6-9展示了如何计算滑动平均数。
例6-9 计算简单滑动平均数
public static double[] simpleMovingAverage(double[] values, int n) {double[] sums = Arrays.copyOf(values, values.length); ➊Arrays.parallelPrefix(sums, Double::sum); ➋int start = n - 1;return IntStream.range(start, sums.length) ➌.mapToDouble(i -> {double prefix = i == start ? 0 : sums[i - n];return (sums[i] - prefix) / n; ➍}).toArray(); ➎}
这段代码有点复杂,我会分步介绍它是如何工作的。参数n是时间窗口的大小,我们据此计算滑动平均值。由于要使用的并行操作会改变数组内容,为了不修改原有数据,在➊处复制了一份输入数据。
在➋处执行并行操作,将数组的元素相加。现在sums变量中保存了求和结果。比如输入0、1、2、3、4、3.5,则计算后的值为0.0、1.0、3.0、6.0、10.0、13.5。
现在有了和,就能计算出时间窗口中的和了,减去窗口起始位置的元素即可,除以n即得到平均值。可以使用已有的流中的方法计算该值,那就让我们来试试吧!使用Intstream.range得到包含所需元素下标的流。
在➍处使用总和减去窗口起始值,然后再除以n得到平均值。最后在➎处将流转换为数组。
