我编写了一个程序,通过将数组拆分为相等的块并使用冒泡排序在单个线程中进行排序,对具有多个线程的数组进行排序。然后我使用合并算法来组合两个数组。
我想将此程序与使用Streams对数组进行排序的程序进行比较。我的问题是,如果我将数组传递到流中,我将如何进行拆分,排序和合并以并行执行排序,但是通过使用并行流而不是创建自己的线程/ runnables等。
有什么想法吗?
答案 0 :(得分:2)
我认为你的问题纯粹是教育性和实验性的,没有任何实际应用,因为有更多有效的方法来对Java中的元素进行排序。如果你想在这里使用Stream API,你可以创建一个spliterator来执行冒泡分类和收集器,它在组合器中执行合并排序。
这里的分裂者:
static class BubbleSpliterator<T> implements Spliterator<T> {
private final Comparator<? super T> cmp;
private final Spliterator<T> source;
private T[] data;
private int offset;
public BubbleSpliterator(Spliterator<T> source, Comparator<? super T> cmp) {
this.source = source;
this.cmp = cmp;
}
@SuppressWarnings("unchecked")
private void init() {
if (data != null)
return;
Stream.Builder<T> buf = Stream.builder();
source.forEachRemaining(buf);
data = (T[]) buf.build().toArray();
bubble(data, cmp);
}
private static <T> void bubble(T[] data, Comparator<? super T> cmp) {
for (int i = 0; i < data.length - 1; i++)
for (int j = i + 1; j < data.length; j++) {
if (cmp.compare(data[i], data[j]) > 0) {
T tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
}
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
init();
if (offset >= data.length)
return false;
action.accept(data[offset++]);
return true;
}
@Override
public void forEachRemaining(Consumer<? super T> action) {
init();
for (int i = offset; i < data.length; i++)
action.accept(data[i]);
offset = data.length;
}
@Override
public Spliterator<T> trySplit() {
if (data != null)
return null;
Spliterator<T> prefix = source.trySplit();
return prefix == null ? null : new BubbleSpliterator<>(prefix, cmp);
}
@Override
public long estimateSize() {
if (data != null)
return data.length - offset;
return source.estimateSize();
}
@Override
public int characteristics() {
return source.characteristics();
}
public static <T> Stream<T> stream(Stream<T> source,
Comparator<? super T> comparator) {
Spliterator<T> spltr = source.spliterator();
return StreamSupport.stream(new BubbleSpliterator<>(spltr, comparator),
source.isParallel()).onClose(source::close);
}
}
它需要源,委托分割到源,但是当请求元素时,它会将源元素转储到数组并对它们执行冒泡排序。您可以这样检查:
int[] data = new Random(1).ints(100, 0, 1000).toArray();
Comparator<Integer> comparator = Comparator.naturalOrder();
List<Integer> list = BubbleSpliterator.stream(Arrays.stream(data).parallel().boxed(), comparator).collect(
Collectors.toList());
System.out.println(list);
结果取决于计算机上的硬件线程数,可能如下所示:
[254, 313, 588, 847, 904, 985, 434, 473, 569, 606, 748, 978, 234, 262, 263, 317, 562, 592, 99, 189, 310,...]
在这里,您可以看到输出包含多个排序序列。此类序列的数量对应于Stream API创建的并行任务的数量。
现在要通过合并排序组合排序的序列,您可以编写一个特殊的收集器,如下所示:
static <T> List<T> merge(List<T> l1, List<T> l2, Comparator<? super T> cmp) {
List<T> result = new ArrayList<>(l1.size()+l2.size());
int i=0, j=0;
while(i < l1.size() && j < l2.size()) {
if(cmp.compare(l1.get(i), l2.get(j)) <= 0) {
result.add(l1.get(i++));
} else {
result.add(l2.get(j++));
}
}
result.addAll(l1.subList(i, l1.size()));
result.addAll(l2.subList(j, l2.size()));
return result;
}
static <T> Collector<T, ?, List<T>> mergeSorting(Comparator<? super T> cmp) {
return Collector.<T, List<T>> of(ArrayList::new, List::add,
(l1, l2) -> merge(l1, l2, cmp));
}
在顺序中,它的工作方式与Collectors.toList()
类似,但并行执行合并排序,假设两个输入列表已经排序。我的mergeSorting实现可能不是最理想的,你可以写一些更好的东西。
因此,要通过Stream API对所有内容进行排序,您可以同时使用BubbleSpliterator
和mergeSorting
收集器:
int[] data = new Random(1).ints(100, 0, 1000).toArray();
Comparator<Integer> comparator = Comparator.naturalOrder();
List<Integer> list = BubbleSpliterator.stream(Arrays.stream(data).parallel().boxed(), comparator).collect(
mergeSorting(comparator));
System.out.println(list);
结果将完全排序。
这个实现多次执行不必要的输入数据复制,所以我猜,自定义泡泡+合并实现可以在性能方面超过这个。
答案 1 :(得分:1)
如果您想使用Java 8 Stream API并行排序数组,这可能会对您有所帮助:
IntStream randomIntegers = ThreadLocalRandom.current().ints(100, 0, 100);
int[] sortedArray = randomIntegers
.parallel() // (1)
.sorted() // (2)
.toArray();
System.out.println(Arrays.toString(sortedArray));
无论你有什么类型的Stream
,
只需调用parallel()
然后调用sorted()
。
(调用的顺序并不重要)
通过跟踪代码我们发现:
final class SortedOps {
private static final class OfInt extends IntPipeline.StatefulOp<Integer> {
//...
@Override
public <P_IN> Node<Integer> opEvaluateParallel(PipelineHelper<Integer> helper,
Spliterator<P_IN> spliterator,
IntFunction<Integer[]> generator) {
if (StreamOpFlag.SORTED.isKnown(helper.getStreamAndOpFlags())) {
return helper.evaluate(spliterator, false, generator);
} else {
Node.OfInt n = (Node.OfInt) helper.evaluate(spliterator, true, generator);
int[] content = n.asPrimitiveArray();
Arrays.parallelSort(content); // <== this
return Nodes.node(content);
}
}
}
private static final class OfRef<T> extends ReferencePipeline.StatefulOp<T, T> {
//...
@Override
public <P_IN> Node<T> opEvaluateParallel(PipelineHelper<T> helper,
Spliterator<P_IN> spliterator,
IntFunction<T[]> generator) {
// If the input is already naturally sorted and this operation
// naturally sorts then collect the output
if (StreamOpFlag.SORTED.isKnown(helper.getStreamAndOpFlags()) && isNaturalSort) {
return helper.evaluate(spliterator, false, generator);
}
else {
// @@@ Weak two-pass parallel implementation; parallel collect, parallel sort
T[] flattenedData = helper.evaluate(spliterator, true, generator).asArray(generator);
Arrays.parallelSort(flattenedData, comparator); // <== this
return Nodes.node(flattenedData);
}
}
}
}
Arrays.parallelSort()
用于对后备阵列进行排序。