前言

  • 首次接触到Stream的时候以为它是和InputStream、OutputStream这样的输入输出流的统称。

流和集合的前世今生

概念的差异

在开发中,我们使用最多的类库之一就是集合。集合是一种内存中的数据结构,用来保存对象数据,集合中的每个元素都得先算出来才能添加到集合中,相比之下:

集合用特定数据结构(如List,Set或Map)存储和分组数据。但是,流用于对存储的数据(例如数组,集合或I / O资源)执行复杂的数据处理操作,例如过滤,匹配,映射等。由我们可以知道:

集合主要是存储数据,而流主要关注对数据的操作。

数据修改

我们可以添加或删除集合中的元素。但是,不能添加或删除流中的元素。流是通过消费数据源的方式,对其执行操作并返回结果。它不会修改数据源头。

内部迭代和外部迭代

Java8提供的 Streams的主要特点是我们不必担心使用流时的迭代。流在后台为我们内部执行迭代。我们只需关注在数据源上需要执行哪些操作即可。

循环遍历

流只能遍历一次。如果你遍历该流一次,则将其消耗掉。要再次遍历它,必须再次从数据源中获取新的流。但是,集合可以遍历多次。

惰性求值(懒汉or饿汉)

相信大家都知道单例模式中的两种模式,懒汉式和饿汉式,在这里也可以相似的理解。

集合以饿汉式迅速的构建,即是所有元素都在开始时就进行了计算。但是,流是延迟构造的,即在调用终端操作之前不会去计算中间操作,也就是惰性求值(懒汉式)。

特性解读

两种迭代方式

上面我们提到了两种迭代的方式,内部迭代和外部迭代,怎么来理解呢?

在java8之前,我们用的for循序,其实就是外部迭代,显示的去循环集合中的每一个元素。

而内部迭代则是,Stream内部帮我们做了这个操作,并且它还把流的值放到了某个地方,我们只需要给出相应的指令(map/flatmap/filter),指挥它就行。

中间操作和终端操作

在java8中,我们可以把中间操作认为是工厂流水线上的一个工人,它将产品加工过后,返回一个新的东西(流),一个新的流。它会让多个操作可以连接起来,一旦流水线上触发一个终端操作就会执行处理。

惰性 求值的理解

上面提到的两种操作其实就是惰性求值的解读。

中间操作一般都可以合并起来,在终端操作时一次性全部处理求值。

在处理更大的数据或流操作很多时,惰性求值是真正的福音。

因为处理数据时,我们不确定如何使用处理后的数据。直接循环一个很大的集合将始终以性能为代价而告终,其实客户端可能只是最终会利用其中的一小部分。或者,根据某些条件过滤一下,它可能甚至不需要利用该数据。惰性求值处理基于按需 策略来帮助我们实现业务功能。

Stream操作案例

String类上提供了有两个新方法:join和chars,使用join拼接字符串非常方便。

String.join(":", "foobar", "foo", "bar");
// => foobar:foo:bar

第二种方法chars为字符串的所有字符创建流,可以对这些字符使用流操作:

"foobar:foo:bar"
.chars()
.distinct()
.mapToObj(c -> String.valueOf((char)c))
.sorted()
.collect(Collectors.joining());
// => :abfor

处理文件

Files最初是在Java 7中作为Java NIO的一部分引入的。JDK 8 API添加了一些其他方法,使我们能够对文件使用功能流。

try (Stream<Path> stream = Files.list(Paths.get(""))) {
String joined = stream
.map(String::valueOf)
.filter(path -> !path.startsWith("."))
.sorted()
.collect(Collectors.joining("; "));
System.out.println("List: " + joined);
}

上面的示例列出了当前工作目录的所有文件,然后将每个路径映射到其字符串表示形式。然后将结果过滤,排序并最终加入一个字符串中。

细心的你您可能已经注意到,流的创建被包装在try / with语句中。流实现了AutoCloseable,在这种情况下,由于有IO操作支持,因此我们确实必须显式关闭流。

查找文件

Path start = Paths.get("");
int maxDepth = 5;
try (Stream<Path> stream = Files.find(start, maxDepth, (path, attr) ->
String.valueOf(path).endsWith(".js"))) {
String joined = stream
.sorted()
.map(String::valueOf)
.collect(Collectors.joining("; "));
System.out.println("Found: " + joined);
}

该方法find接受三个参数:目录路径start是初始起点,并maxDepth定义了要搜索的最大文件夹深度。第三个参数是匹配谓词,它定义搜索逻辑。在上面的示例中,我们搜索所有JavaScript文件(文件名以.js结尾)。

读写文件

List<String> lines = Files.readAllLines(Paths.get("res/nashorn1.js"));
lines.add("print('foobar');");
Files.write(Paths.get("res/nashorn1-modified.js"), lines);

用Java 8将文本文件读入内存并将字符串写入文本文件。这些方法的内存效率不是很高,因为整个文件都将被读取到内存中。文件越大,将使用越多的堆大小。

使用流的注意事项

注意,流只能使用一次。

 public static void main(String[] args) {

        String[] array = {"a", "b", "c", "d", "e"};
Stream<String> stream = Arrays.stream(array); // 消费流
stream.forEach(x -> System.out.println(x)); // 重用流! throws IllegalStateException
long count = stream.filter(x -> "b".equals(x)).count();
System.out.println(count);
}

正确的使用方式

public static void main(String[] args) {

        String[] array = {"a", "b", "c", "d", "e"};

        Supplier<Stream<String>> streamSupplier = () -> Stream.of(array);

        //获取新的流
streamSupplier.get().forEach(x -> System.out.println(x)); //获取另一个流
long count = streamSupplier.get().filter(x -> "b".equals(x)).count();
System.out.println(count); }

过滤空值

   Stream<String> language = Stream.of("java", "python", "node", null, "ruby", null, "php");

        //List<String> result = language.collect(Collectors.toList());

  	//使用filter过滤空值
List<String> result = language.filter(x -> x!=null).collect(Collectors.toList()); result.forEach(System.out::println);

map映射操作

 List<String> alpha = Arrays.asList("a", "b", "c", "d");

        //Java8前
List<String> alphaUpper = new ArrayList<>();
for (String s : alpha) {
alphaUpper.add(s.toUpperCase());
} System.out.println(alpha); //[a, b, c, d]
System.out.println(alphaUpper); //[A, B, C, D] // Java 8
List<String> collect = alpha.stream().map(String::toUpperCase).collect(Collectors.toList());
System.out.println(collect); //[A, B, C, D] // map映射操作
List<Integer> num = Arrays.asList(1,2,3,4,5);
List<Integer> collect1 = num.stream().map(n -> n * 2).collect(Collectors.toList());
System.out.println(collect1); //[2, 4, 6, 8, 10]

分组,计数、排序

 //3 apple, 2 banana, others 1
List<String> items =
Arrays.asList("apple", "apple", "banana",
"apple", "orange", "banana", "papaya"); Map<String, Long> result =
items.stream().collect(
Collectors.groupingBy(
Function.identity(), Collectors.counting()
)
); Map<String, Long> finalMap = new LinkedHashMap<>(); //map排序
result.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue()
.reversed()).forEachOrdered(e -> finalMap.put(e.getKey(), e.getValue())); System.out.println(finalMap);

总结

本篇文章记录了Stream流操作的一些知识点。来检测一下,以下问题你是不是都会了呢?

  • Stream流和集合的区别?
  • 解释内部循环和外部循环?
  • 解释一下惰性求值?
  • Stream的常用操作有哪些?

欢迎来公众号【侠梦的开发笔记】 一起交流进步

侠说java8--Stream流操作学习笔记,都在这里了的更多相关文章

  1. java8 stream流操作的flatMap(流的扁平化)

    https://mp.weixin.qq.com/s/7Fqb6tAucrl8UmyiY78AXg https://blog.csdn.net/Mark_Chao/article/details/80 ...

  2. 还看不懂同事的代码?超强的 Stream 流操作姿势还不学习一下

    Java 8 新特性系列文章索引. Jdk14都要出了,还不能使用 Optional优雅的处理空指针? Jdk14 都要出了,Jdk8 的时间处理姿势还不了解一下? 还看不懂同事的代码?Lambda ...

  3. Java8 Stream流API常用操作

    Java版本现在已经发布到JDK13了,目前公司还是用的JDK8,还是有必要了解一些JDK8的新特性的,例如优雅判空的Optional类,操作集合的Stream流,函数式编程等等;这里就按操作例举一些 ...

  4. Java8——Stream流式操作的一点小总结

    我发现,自从我学了Stream流式操作之后,工作中使用到的频率还是挺高的,因为stream配合着lambda表达式或者双冒号(::)使用真的是优雅到了极致!今天就简单分(搬)享(运)一下我对strea ...

  5. 【JDK8】Java8 Stream流API常用操作

    Java版本现在已经发布到JDK13了,目前公司还是用的JDK8,还是有必要了解一些JDK8的新特性的,例如优雅判空的Optional类,操作集合的Stream流,函数式编程等等;这里就按操作例举一些 ...

  6. 【转】Java8 Stream 流详解

      当我第一次阅读 Java8 中的 Stream API 时,说实话,我非常困惑,因为它的名字听起来与 Java I0 框架中的 InputStream 和 OutputStream 非常类似.但是 ...

  7. Java8 Stream流

    第三章 Stream流 <Java8 Stream编码实战>的代码全部在https://github.com/yu-linfeng/BlogRepositories/tree/master ...

  8. 【java多线程】java8的流操作api和fork/join框架

    原文:https://blog.csdn.net/u011001723/article/details/52794455/ 一.测试一个案例,说明java8的流操作是并行操作 1.代码 package ...

  9. 全面吃透JAVA Stream流操作,让代码更加的优雅

    全面吃透JAVA Stream流操作,让代码更加的优雅 在JAVA中,涉及到对数组.Collection等集合类中的元素进行操作的时候,通常会通过循环的方式进行逐个处理,或者使用Stream的方式进行 ...

随机推荐

  1. 如何利用aop的环绕消息处理log, 以及各种坑的记录

    如何利用aop的环绕消息处理log, 以及各种坑的记录 本文链接: https://www.cnblogs.com/zizaiwuyou/p/11667423.html 因为项目里有很多地方要打log ...

  2. 手把手教你用Python实现自动特征工程

    任何参与过机器学习比赛的人,都能深深体会特征工程在构建机器学习模型中的重要性,它决定了你在比赛排行榜中的位置. 特征工程具有强大的潜力,但是手动操作是个缓慢且艰巨的过程.Prateek Joshi,是 ...

  3. 2018-12-25-SourceYard-制作源代码包

    title author date CreateTime categories SourceYard 制作源代码包 lindexi 2018-12-25 9:43:7 +0800 2018-12-09 ...

  4. H3C 配置NAT Server

  5. 【b703】矩阵取数游戏

    Time Limit: 1 second Memory Limit: 50 MB [问题描述] 帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的n*m的矩阵,矩阵中的每个元素aij均为非负整数.游戏规 ...

  6. java 文件拷贝

    需求:源和目标! 那么我们需要源文件和目标文件! 构建管道的时候就需要两个:输出流和输入流管道! Eg: package july7file; //java7开始的自动关闭资源 import java ...

  7. vue基于 element-ui 实现菜单动画效果,任意添加 li 个数均匀撑满 ul 宽度

    %)%)%%%))) .) .) .) .) .) .) .) .) .) .) .) .) .) .) .) .% %% %deg);}

  8. UVa1601 - The Morning after Halloween [单向bfs]

    解题思路: 1.注意到2*2方格中必有一个#,那么最多只有192条通道,可以将所有非‘#’的位置提取出来用邻接表的方式建图,通过bfs搜索目标位置. 2.将三个ghost的位置(a,b,c)作为状态量 ...

  9. HDU 1540 Tunnel Warfare (线段树)

    Tunnel Warfare Problem Description During the War of Resistance Against Japan, tunnel warfare was ca ...

  10. js New一个函数和直接调用的区别

    使用New是构造函数,不使用New是函数调用,同时this指向不同. 示例: function Test(name, age, job) { console.log(this); this.name ...