Java 8:按值聚合项目列表并按位置范围重新组合

时间:2017-05-20 19:58:09

标签: java-8 java-stream

我不想使用Stream API进行排序并以整齐的方式创建列表。也许用Stream直接做到这一点是不可能的。 最终目标是对数据进行统计。但这不是问题所在。

源来自 ffprobe ,它是一个简单的框架信息列表:位置,类型(I或B / P)以及统计信息的其他信息。 解析没问题:我有一个很大的框架列表

框架就像:

int position = 454
enum type = I
... // others datas for stats

好的,现在我不想通过GOP列表重新组合帧。 GOP,“图片组”是视频帧列表。它始终以帧类型“I”开始。 GOP可以像IPBBPBBPBBP一样。大小(帧的nb)和顺序可以在视频流中改变。我无法预测它。

我如何恢复GOP清单?

1) I start my first GOP with the first frame (position = 0, type = I).
2) for each next frames, I check the type. If type = B or P, I add this frame to my current GOP.
3) but if this frame type is a "I", I "close" the current GOP list, create a new GOP list, and I add this frame. Goto 2) as long as there are frames.

好吧,使用 for loop 来实现它并不太复杂。

但是,有了Stream和它的朋友(收藏家),我怎么能这样做?!这可以比老式的 更快吗?

感谢阅读并度过了美好的一天。

PS:这是一个开源应用程序。

编辑:经过简单的测试,for循环保持非常快达155000帧:在一个简单的i7核心上,74毫秒用于完成工作(并且花费更少的时间来打开数据文件)。 所以...似乎不需要流API。 但现在,尤金的回答非常有启发性。谢谢Eugene!

3 个答案:

答案 0 :(得分:2)

我已经简化了你的输入以进行测试。所以这就是我想象的Frame

static class Frame {

    private final int position;

    private final String type;

    public Frame(int position, String type) {
        super();
        this.position = position;
        this.type = type;
    }

    public int getPosition() {
        return position;
    }

    public String getType() {
        return type;
    }

    @Override
    public String toString() {
        return "pos = " + position + " type = " + type;
    }

}

以下是我将如何解决它:

List<Frame> list = 
         Arrays.asList(
              new Frame(0, "I"),
              new Frame(1, "G"), 
              new Frame(2, "B"), 
              new Frame(3, "I"), 
              new Frame(4, "B"));

    int[] indexes = IntStream.concat(IntStream.range(0, list.size())
            .filter(i -> list.get(i).getType().equals("I")),  
                    IntStream.of(list.size()))
            .toArray();

    List<List<Frame>> frames = IntStream.range(0, indexes.length - 1)
            .mapToObj(x -> list.subList(indexes[x], indexes[x + 1]))
            .collect(Collectors.toList());

结果将是:

[[pos = 0 type = I, pos = 1 type = G, pos = 2 type = B], 
   [pos = 3 type = I, pos = 4 type = B]]

indexes将捕获索引,其中“I”也附加列表的大小。而不是一个简单的子列表来获得所需的列表。

现在这个不会像for循环一样快,所以这里没有性能提升。通常,流比通常的for循环慢。使用parallel流时,您可以获得唯一的性能提升 - 但您需要列表中批次的元素。

答案 1 :(得分:1)

不要混淆另一个答案,我想(我发誓我已经在某个地方看到了这个!)显示另一个自定义收藏家。

List<List<Frame>> custom2 = list.stream()
            .collect(Collector.of(
                    () -> {
                        List<List<Frame>> supList = new ArrayList<>();
                        supList.add(new ArrayList<>());
                        return supList;
                    },
                    (l, frame) -> {
                        if (frame.getType().equals("I")) {
                            l.add(new ArrayList<>());   
                        }
                        l.get(l.size() - 1).add(frame);
                    },
                    (left, right) -> {
                        List<Frame> first = right.remove(0);
                        left.get(left.size() - 1).addAll(first);
                        left.addAll(right);
                        return left;
                    },
                    result -> {
                        result.remove(0);
                        return result;
                    }));

我将尝试用视觉(对于并行流)解释这里发生的事情。假设输入为:

IPBIHGIR

这就是并行收集器的用途。

[() (I)]   [(P)]   [(B)]  [() (I)]   [(H)]   [(G)]   [() (I)]   [(R)]  
  \         /        \       /         \       /        \         / 
   \       /          \     /           \     /          \       /
  [() (I, P)]        [(B) (I)]          [(H, G)]        [() (I, R)]
       \                 /                  \               /                
        \               /                    \             /
         \             /                      \           /
          \           /                        \         /
         [() (I,P,B) (I)]                     [(H,G) (I, R)]
               \                                    /
                \                                  /  
                 \                                /
                  \                              /
                   \                            /
                    \                          /
                    [(), (I,P,B) (I,H,G), (I,R)]

finisher只修剪第一个空数组。

我再说一遍再次:我已经在某个地方看到了这个(当时需要一段时间才能理解),因此绘图 - 可能会帮助其他人。

如果您阅读本文并了解原始来源,请发表评论,我很乐意将其写入答案,以便人们了解这一点的真正创造者......

答案 2 :(得分:0)

经过简单的测试,for循环可以快速达到155000帧:在一个简单的i7核心上,74毫秒用于完成工作(并且花费更少的时间来打开数据文件)。所以...似乎不需要流API。但现在,尤金的回答非常有启发性。谢谢尤金!