Stream流由浅到深

2022-07-25,

Stream流由浅到深–持续更新中

从迭代到流操作

​ 在处理集合时,我们通常会迭代遍历他的元素,并在每个元素上执行某项操作时。

普通迭代操作
Path path = Paths.get("E:\\JavaCode\\fanshe\\src\\main\\resources\\StreamTest.txt");
String contents = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
List<String> words=Arrays.asList(contents.split("\\PL+"));

long count = 0;
for(String w : words )
{
	if(w.length()>12) count++;
}
流式操作
long count = words.stream()
    .filter(w -> w.length() > 12)
    .count();

​ 流的版本比循环也好更易于阅读,因此我们不需要扫描整个代码去查找过滤器和计数器操作,方法名就可以告诉我们其代码意欲何为。而且循环需要非常详细的指定操作的顺序,而流却能够以其想要的任何方式来调度这些操作,只要结果是正确的即可。

​ 仅以stream修改为parallelStream就可以让流库以并行方式来执行过滤和计数。

long count=words.parallelStream()
	.filter(w -> w.length()>12)
	.count();

​ 流遵循了“做什么而非怎么做”的原则。在示例中,我们没有指定该操作以什么顺序或者在哪个线程中执行,相比之下,迭代则要确切指出计算该如何工作,因此就丧失了进行优化的机会。流表面看气力啊和集合很类似,都可以让我们转换和获取数据。但是他们之间存在着显著的差异:

1.流并不存储其元素

2.流的操作不会修改其数据源

3.流的操作是尽可能惰性执行的

流的创建

  public static <T> void show(String title, Stream<T> stream) {
        final int SIZE = 10;
        List<T> firstElements = stream.limit(SIZE + 1).collect(Collectors.toList());
        System.out.print(title + ": ");
        for (int i = 0; i < firstElements.size(); i++) {
            if (i > 0) System.out.print(", ");
            if (i < SIZE) System.out.print(firstElements.get(i));
            else System.out.print("...");
        }
        System.out.println();
    }

    public static void main(String[] args) throws IOException {
        Path path = Paths.get("E:\\JavaCode\\fanshe\\src\\main\\resources\\StreamTest.txt");
        String contents = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);

        Stream<String> word = Stream.of(contents.split("\\PL+"));
        show("word", word);

        Stream<String> song = Stream.of("gently", "down", "the", "stream");
        show("song", song);

        Stream<String> silence = Stream.empty();
        show("silence", silence);

        Stream<String> echos = Stream.generate(() -> "echo");
        show("echos", echos);

        Stream<Double> random = Stream.generate(Math::random);
        show("random", random);

        Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
        show("integers", integers);

        Stream<String> wordsAnotherWay = Pattern.compile("\\PL+").splitAsStream(contents);
        show("wordsAnotherWay", wordsAnotherWay);

        try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
            show("lines", lines);
        }
    }

上面的示例展示了各种创建流的方式

  • static < T> Stream< T> of(T… values) 产生一个元素为给定值的流
  • static < T> Stream< T> empty() 产生一个不包含任何元素的流
  • static < T> Stream< T> generate(Supplier< T> s) 产生一个无限流,他的值是通过反复调用函数s而创建的
  • static < T> Stream< T> iterate(T seed, UnaryOperator< T> f) 产生一个无限流,他的元素包含种子、在种子上调用f产生的值、在前一个元素上调用f产生的值,等等。
  • static < T> Stream< T> stream(T[] array, int startInclusive, int endExclusive) 产生一个流,他的元素是由数组中指定范围内的元素构成的
  • Stream< String> splitAsStream(CharSequence input) 产生一个流,他的元素是由输入总由该模式界定的部分
  • static Stream< String> lines(Path path)
  • static Stream< String> lines(Path path, Charset cs) 产生一个流,他的元素是指定文件中的行,该文件的字符集为UTF-8,或者为指定的字符集
  • T get() 提供一个值

filter、map和flatMap方法

流的转化会产生一个新的流,他的元素派生自另一个流元素。fileter转化会产生一个流,他的元素与某种条件相匹配。

List<String> wordList= ...;
Stream<String> longWords= wordList.stream().filter(w -> w.length() > 12);

fileter的引元是Predicate,即从T到boolean的函数。通常我们想要按照某种方式来转化流中的值,此时,可以使用map方法并传递执行该转换的函数。

Stream<String> lowercaseWords = words.stream().map(String::toLowerCase);
这里我们使用的是带有方法引用的map,通常我们可以使用lambda表达式来代替
Stream<String> firstLetters = words.stream().map(s -> s.substring(0,1));

在使用map时,会有一个函数应用到每个元素上,并且其结果是包含了应用该函数后所产生的所有结果的流。假设我们有个函数,他返回的不是一个值,而是一个包含众多值的流:

//注意这个方法在文档中会多次出现
public static Stream<String> letters(String s){
	List<String> result = new ArrayList<>();
    for(int i = 0; i < s.length(); i++)
        result.add(s.substring(i, i + 1));
    return result.stream();
}

假设letters(“boat”)的返回值是流[“b”,“o”,“a”,“t”]

Stream<Stream<String>> result=words.stream().map(w -> letters(w));

执行本行代码就会得到一个包含流的流,类似于[…[“y”,“o”,“u”,“r”],[“b”,“o”,“a”,“t”],…],为了将其摊平为字母流[…,“y”,“o”,“u”,“r”,“b”,“o”,“a”,“t”,…],可以使用flatMap方法而不是map方法

Stream<String> flatResult = words.stream().flatMap(w -> letters(w));
	//Calls letters on each word and flattens the results

注意:在流之外的类中你也会发现flatMap方法,因为他是计算机科学中的一种通用概念,假设我们有一个泛型G(例如Stream),以及将某种类型T转换为G< U>的函数f和将类型U转换为G< V>的函数G。然后我们可以通过使用flatMap来组合他们,即首先应用f,然后应用g。这是单子论的关键概念,但是不必担心,我们无需了解任何有关单子论的知识就可以使用flatMap。

  • Stream< T> filter(Predicate<? super T> predicat) 产生一个流,他包含当前刘中所有满足断言条件的元素
  • < R> Stream< R> map<Function<? super T, ? extends R> mapper) 产生一个流,他包含将mapper应用于当前流中所有元素产生的结果
  • < R> Stream< R> flatMap<Function<? super T, ? extends Stream<? extends R>> mapper) 产生一个流,他是通过将mapper应用于当前流中所有元素所产生的结果连接到一起而获得的。(注意,这里的每个结果都是一个流)

抽取子流和连接流

调用stream.limit(n) 会返回一个新的流,他在n个元素之后结束(如果原来的流更短,那么就会在流结束时结束)。这个方法对于裁剪无限流的尺寸会显得特别有用。下面这个示例方法会产生一个包含100个随机数的流。

Stream<Double> ramdoms = Stream.generate(Math::random).limit(100);

调用stream.skip(n) 正好相反:他会丢弃前n个元素,这个方法在将文本分隔为单词时会显得很方便,因为按照split方法的工作方式,第一个元素是没有什么用的空字符串。

Stream<String> words = Stream.of(contents.split("\\PL+")).skip(1);

通过limt和skip我们就得到了两个流,这个时候如果我们需要将两个流进行合并,那么我们可以用Stream类静态的concat方法将两个流连接起来。

Stream<String> combined = Stream.concat(letters("Hello"),letters("world"));
	//Yields the stream ["H","e","l","l","o","w","o","r","l","d"]

当然,第一个流不能是无限流,否则第二个流永远都不会得到处理的机会

  • Stream< T> limit(long maxSize) 产生一个流,其中包含了当前流中最初的maxSize个元素
  • Stream< T> skip(long n) 产生一个流,他的元素是当前流中出了前n个元素之外的所有元素
  • static < T> Stream< T> concat(Stream<? extends T> a,Stream<? extends T> b) 产生一个流,他的元素是a的元素后面跟着b的元素

其他流转换

简单约简

Optional类型

收集结果收集到映射表中

群组和分区

下游收集齐

简约操作

基本类型流

并行流

本文地址:https://blog.csdn.net/weixin_40395050/article/details/111823829

《Stream流由浅到深.doc》

下载本文的Word格式文档,以方便收藏与打印。