分离器并行排序顺序

时间:2019-07-04 15:37:15

标签: java java-8 spliterator

我正在实现一个分页的分隔符(在Java中),该分隔符应允许并行访问。

我有以下测试用例(测试在带有Spock的Groovy中进行):

def 'parallel, two pages'()
{
    when: 'a sorted range from 0 to 6'
    def fetcher = new IntegerRangePageFetcher(6)

    and: 'a spliterator with a page size of 5'
    def spliterator = new PagedSpliterator(fetcher, 5)

    and: 'a stream with the given range is collected to a list'
    def result = StreamSupport
            .stream(spliterator, true)
            .collect(Collectors.toList())

    then: 'the sort order is obeyed'
    expect result, contains(0, 1, 2, 3, 4, 5)
}

此测试用例失败,并出现以下错误:

Condition not satisfied:

expect result, contains(0, 1, 2, 3, 4, 5)
|      |
false  [5, 0, 1, 2, 3, 4]

Expected: iterable containing [<0>, <1>, <2>, <3>, <4>, <5>]
     but: item 0: was <5>

分隔符具有characteristics()

return IMMUTABLE | ORDERED | SIZED | SUBSIZED | NONNULL;

当我不使用并行时,代码有效。所以我听不懂ORDERED

  • 如果已设置,则在使用并行生成的块时,流框架是否应保证顺序并应对结果进行排序?如果是,为什么不对我进行排序?
  • 还是我的trySplit实现中出现错误,并且必须按照给定的顺序拆分? (当前,我在打开的页面中间进行拆分,0-mid停留在当前的拆分器中,中端在新创建的拆分器中)
  • 还是我应该在sort()之前致电collect(),因为该框架根本无法保证任何顺序?

---根据反馈进行更新---

感谢您的回答,我的代码中有两个逻辑错误。首先是请求的代码段:

@Override
public Spliterator<T> trySplit()
{
    // first query
    if (pageIterator == null) {
        pageIterator = pageFetcher.fetchNextPage(paginationInfo);
    }

    // delegate split decision
    var newPaginationInfo = paginationInfo.split();
    if (newPaginationInfo == null) {
        log.info("* Spliterator returns null");
        return null;
    }

    // now we split
    var newSpliterator = new PagedSpliterator<>(pageFetcher, newPaginationInfo);
    return newSpliterator;
}

public PaginationInfo split()
{
    // when open range or nothing left we don't split
    if ((endElementIndex == -1) || !hasNextPage()) {
        return null;
    }

    // calculate the splitting position
    var firstHalfPages = (getEndPageIndex() - getNextPageIndex()) / 2;
    var midElementIndex = (getNextPageIndex() + firstHalfPages) * pageSize;

    // create an additional PaginationInfo and set the ranges according to the split position
    var newPaginationInfo = new PaginationInfo(this);
    newPaginationInfo.firstElementOnNextPageIndex = midElementIndex;
    newPaginationInfo.nextElementIndex = midElementIndex;

    endElementIndex = midElementIndex;

    return newPaginationInfo;
}

第一个错误:

新创建的Spliterator设置为第二个范围而不是第一个范围。我在文档中阅读了有关前缀的信息,但对我来说却很笨拙。我按页面大小拆分以具有多个并行请求。在开始时(第一个分割器实例),我必须获取第一页以获得页和元素计数器。因此,要解决订单问题,我必须将从第一个拆分器获取的数据分发到第二个拆分器以服从订单,这对我来说很奇怪而且不直观。

第二个错误:

    // first query
    if (pageIterator == null) {
        pageIterator = pageFetcher.fetchNextPage(paginationInfo);
    }

所有随后创建的拆分器将收到来自框架的estimateSize()trySplit()调用。当前,在此调用期间,我获取了一个页面,但这会阻止并行性,该获取必须在以后的tryAdvance()调用中进行。

我将实施这些更改,然后再回头给您。

2 个答案:

答案 0 :(得分:1)

是的,您的trySplit中有一个错误。 Spliterator.trySplit的文档指定,如果您具有ORDERED特征,则返回的分隔符必须包含元素的前缀。在返回的拆分器和拆分的其余内容之间切换。

答案 1 :(得分:1)

摘自trySplit的文档:

  

如果此分隔符为ORDERED,则返回的分隔符必须包含元素的严格前缀

您的实现:

  

... 0-mid停留在当前分隔符上,中端停留在新创建的分隔符中

您可以从此处连接适当的点。