欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Stream流由浅到深

程序员文章站 2022-09-28 22:32:29
Stream流由浅到深–持续更新中从迭代到流操作​在处理集合时,我们通常会迭代遍历他的元素,并在每个元素上执行某项操作时。普通迭代操作Path path = Paths.get("E:\\JavaCode\\fanshe\\src\\main\\resources\\StreamTest.txt");String contents = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);List...

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