如何将字符串拆分为字符串流?

Posted

技术标签:

【中文标题】如何将字符串拆分为字符串流?【英文标题】:How to split a String into a Stream of Strings? 【发布时间】:2017-04-17 09:35:54 【问题描述】:

将字符串拆分为流的最佳方法是什么?

我看到了这些变化:

    Arrays.stream("b,l,a".split(",")) Stream.of("b,l,a".split(",")) Pattern.compile(",").splitAsStream("b,l,a")

我的优先事项是:

稳健性 可读性 性能

一个完整的、可编译的example:

import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class HelloWorld 

    public static void main(String[] args) 
        stream1().forEach(System.out::println);
        stream2().forEach(System.out::println);
        stream3().forEach(System.out::println);
    

    private static Stream<String> stream1() 
        return Arrays.stream("b,l,a".split(","));
    

    private static Stream<String> stream2() 
        return Stream.of("b,l,a".split(","));
    

    private static Stream<String> stream3() 
        return Pattern.compile(",").splitAsStream("b,l,a");
    


【问题讨论】:

您是否认为差异如此之大以至于花时间思考“最佳”方式是有意义的,或者您是否认为这是您程序中的性能热点,至少在某种程度上是合理的试图找到“最好”的方式? 请注意,Stream.of() 将在内部调用Arrays.stream(),因此显然这不是“最好的”。 恕我直言,最后一个是最好的。没有嵌套的括号,清楚的是它是关于正则表达式的,它不会创建所有段的中间列表或数组。但这只是我的看法。 我认为我们不能特别判断他想知道这些信息的原因。虽然最好是一种意见,但您的意思是最快的吗? @Prospero OP 清楚地说明了他在寻找什么:稳健性、可读性和性能。 【参考方案1】:

Arrays.stream/String.split

由于String.split 返回一个数组String[],我总是推荐Arrays.stream 作为通过数组流式传输的规范习语。

String input = "dog,cat,bird";
Stream<String> stream = Arrays.stream(input.split( "," ));
stream.forEach(System.out::println);

Stream.of/String.split

Stream.of 是一个 varargs 方法,它恰好接受一个数组,因为 varargs 方法是通过数组实现的,并且在将 varargs 引入 Java 和现有方法时存在兼容性问题改装以接受可变参数。

Stream<String> stream = Stream.of(input.split(","));     // works, but is non-idiomatic
Stream<String> stream = Stream.of("dog", "cat", "bird"); // intended use case

Pattern.splitAsStream

Pattern.compile(",").splitAsStream(string) 具有直接流式传输而不是创建中间数组的优势。因此,对于大量子字符串,这可以带来性能优势。另一方面,如果分隔符是微不足道的,即单个文字字符,String.split 实现将通过快速路径而不是使用正则表达式引擎。所以在这种情况下,答案并不简单。

Stream<String> stream = Pattern.compile(",").splitAsStream(input);

如果流发生在另一个流中,例如.flatMap(Pattern.compile(pattern) ::splitAsStream) 的优点是模式只需分析一次,而不是针对外部流的每个字符串。

Stream<String> stream = Stream.of("a,b", "c,d,e", "f", "g,h,i,j")
    .flatMap(Pattern.compile(",")::splitAsStream);

这是expression::name 形式的方法引用的一个属性,它将在创建函数接口的实例时评估表达式并捕获结果,如What is the equivalent lambda expression for System.out::println 和java.lang.NullPointerException is thrown using a method-reference but not a lambda expression 中所述

【讨论】:

.flatMap(Pattern.compile(pattern) 为什么模式只分析一次?有缓存吗?如果是,这是否写在某个地方的文档中? @Roland:这是expression::name 形式的方法引用的一个属性,表达式将被评估并由创建的函数实例捕获结果。对于Pattern.compile(pattern)::splitAsStream,表达式为Pattern.compile(pattern)。这与例如(string) -&gt; Pattern.compile(pattern).splitAsStream(string) 将重新评估每个函数评估的模式。见here 和here… @Benj 问题在于它甚至调用了subSequence(…).toString(),因此使用不同的CharSequence 实现(即非复制CharBuffer.wrap(string))只会将复制推迟到后续的@987654357 @ 称呼。但是对于标记化,无论如何,您宁愿想要 Java 9 的 Matcher.results()Scanner.findAll(…),所以请考虑 this answer 的后向端口。由于MatchResult 提供了start()end(),因此您可以在其之上构建无副本操作。 @Benj 您还可以考虑this answer 的后半部分,其中包含标记化的代码示例,它为每个唯一标记只创建一个String 实例,这大大减少了时间和内存占用在解析出现大量常见关键字的内容时。 看来 Guava 的 Splitter 有一个有趣的方法,我或多或少地偶然发现了这个。【参考方案2】:

关于 (1) 和 (2) 应该没有太大区别,因为您的代码几乎相同。 关于(3),这在内存(不一定是 CPU)方面会更有效,但在我看来,阅读起来有点困难。

【讨论】:

【参考方案3】:

稳健性

我认为这三种方法的稳健性没有区别。

可读性

我不知道有任何关于代码可读性的可靠科学研究涉及经验丰富的 Java 程序员,所以可读性是一个见仁见智的问题。即便如此,您也永远不知道发表意见的人是否客观地区分了实际可读性、他们所学到的关于可读性的内容以及他们自己的个人品味。

因此,我将由您自己对可读性做出判断……请注意,您确实认为这是一个高优先级。

FWIW,对此事发表意见的只有你和你的团队。

性能

我认为答案是仔细地对三个备选方案进行基准测试。 Holger 提供了基于他对 Java 某些版本的研究的分析。但是:

    他无法确定哪个最快。 严格来说,他的分析仅适用于他所研究的 Java 版本。 (他的分析的某些方面可能在(例如)android Java 或某些未来的 Oracle / OpenJDK 版本上有所不同。) 相对性能可能取决于要拆分的字符串的长度、字段的数量以及分隔符正则表达式的复杂性。 在实际应用程序中,相对性能还可能取决于您对 Stream 对象所做的操作、您选择的垃圾收集器(因为不同的版本显然会产生不同数量的垃圾)以及其他问题。

因此,如果您(或其他任何人)真的关心性能,您应该编写一个微基准测试并在您的生产平台上运行它。然后进行一些特定于应用程序的基准测试。您应该考虑寻找不涉及流的解决方案。

【讨论】:

我同意你的观点:很难科学地证明健壮性、可读性和 Java 性能。不过需要注意的是:可读性不应该只是当前团队的个人品味,因为未来的团队成员也必须阅读代码。 我试图说明可读性和个人品味不是一回事。

以上是关于如何将字符串拆分为字符串流?的主要内容,如果未能解决你的问题,请参考以下文章

如何将字符串中的“\t”拆分为两个单独的字符“\”和“t”? (如何拆分转义序列?)[重复]

如何将字符串拆分为字符串和整数? [复制]

如何将字符串拆分为列表?

如何将字符串拆分为多个部分?

将字符串拆分为字符串数组[重复]

如何将字符串拆分为列表?