当前位置: 首页 > 知识库问答 >
问题:

如何正确地将流减少到另一个流

韦寒
2023-03-14

我有一连串的弦和空值

Stream<String> str1 = Stream.of("A","B","C",null,null,"D",null,"E","F",null,"G",null);

我想将它简化为另一个流,其中任何非空字符串序列连接在一起,即像

Stream<String> str2 = Stream.of("ABC", "", "D", "EF","G")

我发现的第一种方法是创建收集器,首先将完整的输入流减少到具有所有连接字符串列表的单个对象,然后从中创建新流:

class Acc1 {
  final private List<String> data = new ArrayList<>();
  final private StringBuilder sb = new StringBuilder();

  private void accept(final String s) {
    if (s != null) 
      sb.append(s);
    else {
      data.add(sb.toString());
      sb.setLength(0);
    }
  }

  public static Collector<String,Acc1,Stream<String>> collector() {
    return Collector.of(Acc1::new, Acc1::accept, (a,b)-> a, acc -> acc.data.stream());
  }
}
...
Stream<String> str2 = str.collect(Acc1.collector());

但在这种情况下,在任何使用前,如果str2,甚至作为str2。findFirst(),将完全处理输入流。它需要耗费时间和内存的操作,并且在来自某个生成器的无限流上,它将根本不工作

另一种方法-创建将保持中间状态的外部对象并将其使用在平图()中:

class Acc2 {
  final private StringBuilder sb = new StringBuilder();

  Stream<String> accept(final String s) {
    if (s != null) {
      sb.append(s);
      return Stream.empty();
    } else {
      final String result = sb.toString();
      sb.setLength(0);
      return Stream.of(result);
    }
  }
}
...
Acc2 acc = new Acc2();
Stream<String> str2 = str1.flatMap(acc::accept);

在这种情况下,将仅检索通过str2真正访问的元素。

但是使用在流处理之外创建的外部对象对我来说看起来很难看,并且可能会导致一些副作用,我现在看不到。此外,如果str2稍后与并行流()一起使用,它将导致不可预测的结果。

还有什么更正确的流实现吗-

共有2个答案

百里伟
2023-03-14

使用标准流API很难实现此类场景。在我的免费StreamEx库中,我用一些方法扩展了标准流接口,这些方法允许执行所谓的“部分减少”,这正是这里所需要的:

StreamEx<String> str1 = StreamEx.of("A","B","C",null,null,"D",null,"E","F",null,"G",null);
Stream<String> str2 = str1.collapse((a, b) -> a != null,
                          MoreCollectors.filtering(Objects::nonNull, Collectors.joining()));
str2.map(x -> '"'+x+'"').forEach(System.out::println);

输出:

"ABC"
""
"D"
"EF"
"G"

StreamEx。collapse()方法使用提供的收集器部分减少流。第一个参数是一个谓词,应用于两个相邻的原始项,如果必须将它们一起归约,则应返回true。这里我们只要求配对中的第一个不为null((a,b)-

这种实现是完全懒惰的,对并行处理非常友好。

陶锋
2023-03-14

归约或其可变变体收集始终是一个处理所有项目的操作。您的操作可以通过自定义拆分器来实现,例如。

public static Stream<String> joinGroups(Stream<String> s) {
    Spliterator<String> sp=s.spliterator();
    return StreamSupport.stream(
        new Spliterators.AbstractSpliterator<String>(sp.estimateSize(), 
        sp.characteristics()&Spliterator.ORDERED | Spliterator.NONNULL) {
            private StringBuilder sb = new StringBuilder();
            private String last;

            public boolean tryAdvance(Consumer<? super String> action) {
                if(!sp.tryAdvance(str -> last=str))
                    return false;
                while(last!=null) {
                    sb.append(last);
                    if(!sp.tryAdvance(str -> last=str)) break;
                }
                action.accept(sb.toString());
                sb=new StringBuilder();
                return true;
            }
        }, false);
}

它产生了预期的组,你可以用它来测试

joinGroups(Stream.of("A","B","C",null,null,"D",null,"E","F",null,"G",null))
    .forEach(System.out::println);

但也有期望的懒惰行为,可通过

joinGroups(
    Stream.of("A","B","C",null,null,"D",null,"E","F",null,"G",null)
          .peek(str -> System.out.println("consumed "+str))
).skip(1).filter(s->!s.isEmpty()).findFirst().ifPresent(System.out::println);

经过再三思考,我找到了这个稍微高效的变体。只有当至少有两个字符串要连接时,它才会合并StringBuilder,否则,它只会使用已经存在的唯一字符串实例或文字字符串作为空组:

public static Stream<String> joinGroups(Stream<String> s) {
    Spliterator<String> sp=s.spliterator();
    return StreamSupport.stream(
        new Spliterators.AbstractSpliterator<String>(sp.estimateSize(), 
        sp.characteristics()&Spliterator.ORDERED | Spliterator.NONNULL) {
            private String next;

            public boolean tryAdvance(Consumer<? super String> action) {
                if(!sp.tryAdvance(str -> next=str))
                    return false;
                String string=next;
                if(string==null) string="";
                else if(sp.tryAdvance(str -> next=str) && next!=null) {
                    StringBuilder sb=new StringBuilder().append(string);
                    do sb.append(next);while(sp.tryAdvance(str -> next=str) && next!=null);
                    string=sb.toString();
                }
                action.accept(string);
                return true;
            }
        }, false);
}
 类似资料:
  • 我正在用Stream学习Java,我有一张

  • 问题内容: 问:我怎样才能从读到的一切入的方式是不是一个手工制作的循环用我自己的字节的缓冲区? 问题答案: 编写一个方法来执行此操作,然后从需要该功能的任何地方调用它。番石榴已经在中提供了代码。我敢肯定,几乎所有其他具有“通用” IO功能的库也都有它,但是Guava是我第一个“入门”库。它震撼了:)

  • 创建一个有很多高质量图像的应用程序,我决定将图像缩小到所需的大小(这意味着如果图像比屏幕大,我会缩小它)。 我注意到,在一些设备上,如果图像被缩小,它们会变得模糊/像素化,但是在相同的设备上,对于相同的目标图像视图大小,如果图像没有缩小,它们看起来很好。 我决定进一步检查这个问题,并创建了一个小的POC应用程序来显示这个问题。 在向您展示代码之前,下面是我所说内容的演示: 很难看出区别,但是你可以

  • 问题内容: 假设我有一个布尔值流,而我正在编写的reduce操作是||(OR)。我是否可以这样编写它:如果true遇到值,则放弃对至少某些元素的求值? 我正在寻找某种程度的优化(也许是并行流),不一定要完全优化,尽管后者会很棒。 问题答案: 我怀疑您想要这种构造。 你可以看一下 Stream.of(1, 2, 3, 4).peek(System.out::println).anyMatch(i -

  • 问题内容: 我需要将Java转换为的实例(包括地图内容) 我应该怎么做才能使此代码可编译? 问题答案: 从Collectors.toMap(…)javadoc: 例如:

  • 给出这个简化的示例代码: 如何实现reduce操作的结果也是空的?