Java 8 Stream流强大的原理

目录
  • 1、Stream的组成与特点
  • 2、BaseStream接口
  • 3、Stream接口
  • 4、关闭流操作
  • 5、并行流和串行流
  • 6、ParallelStream背后的男人:ForkJoinPool
  • 7、用ForkJoinPool的眼光来看ParallelStream
  • 8、并行流的性能
  • 9、NQ模型
  • 10、遇到顺序

前言:

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

1、Stream的组成与特点

Stream(流)是一个来自数据源的元素队列并支持聚合操作:

  • 元素是特定类型的对象,形成一个队列。Java中的Stream并_不会_向集合那样存储和管理元素,而是按需计算
  • 数据源流的来源可以是集合Collection、数组ArrayI/O channel, 产生器generator
  • 聚合操作类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。这样做可以对操作进行优化, 比如延迟执行(laziness evaluation)和短路( short-circuiting)
  • 内部迭代:以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。Stream提供了内部迭代的方式, 通过访问者模式 (Visitor)实现。

和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。

Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。

Java 的并行 API 演变历程基本如下:

1.0-1.4 中的 java.lang.Thread

5.0 中的 java.util.concurrent

6.0 中的 Phasers 等

7.0 中的 Fork/Join 框架

8.0 中的 Lambda

Stream具有平行处理能力,处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个操作:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);numbers.parallelStream()       .forEach(out::println);

可以看到一行简单的代码就帮我们实现了并行输出集合中元素的功能,但是由于并行执行的顺序是不可控的所以每次执行的结果不一定相同。

如果非得相同可以使用forEachOrdered方法执行终止操作:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);numbers.parallelStream()       .forEachOrdered(out::println);

这里有一个疑问,如果结果需要有序,是否和我们的并行执行的初衷相悖?是的,这个场景下明显无需使用并行流,直接用串行流执行即可, 否则性能可能更差,因为最后又强行将所有并行结果进行了排序。

OK,下面我们先介绍一下Stream接口的相关知识。

2、BaseStream接口

Stream的父接口是BaseStream,后者是所有流实现的顶层接口,定义如下:

public interface BaseStream<T, S extends BaseStream<T, S>>        extends AutoCloseable {    Iterator<T> iterator();    Spliterator<T> spliterator();    boolean isParallel();    S sequential();    S parallel();    S unordered();    S onClose(Runnable closeHandler);    void close();}

其中,T为流中元素的类型,S为一个BaseStream的实现类,它里面的元素也是T并且S同样是自己:

S extends BaseStream<T, S>

是不是有点晕?

其实很好理解,我们看一下接口中对S的使用就知道了:如sequential()、parallel()这两个方法,它们都返回了S实例,也就是说它们分别支持对当前流进行串行或者并行的操作,并返回「改变」后的流对象。

如果是并行一定涉及到对当前流的拆分,即将一个流拆分成多个子流,子流肯定和父流的类型是一致的。子流可以继续拆分子流,一直拆分下去…

也就是说这里的S是BaseStream的一个实现类,它同样是一个流,比如StreamIntStreamLongStream等。

3、Stream接口

再来看一下Stream的接口声明:

public interface Stream<T> extends BaseStream<T, Stream<T>>

参考上面的解释这里不难理解:即Stream可以继续拆分为Stream,我们可以通过它的一些方法来证实:

Stream<T> filter(Predicate<? super T> predicate);<R> Stream<R> map(Function<? super T, ? extends R> mapper);<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);Stream<T> sorted();Stream<T> peek(Consumer<? super T> action);Stream<T> limit(long maxSize);Stream<T> skip(long n);...

这些都是操作流的中间操作,它们的返回结果必须是流对象本身。

4、关闭流操作

BaseStream 实现了 AutoCloseable 接口,也就是 close() 方法会在流关闭时被调用。同时,BaseStream 中还给我们提供了onClose()方法:

S onClose(Runnable closeHandler);

AutoCloseableclose()接口被调用的时候会触发调用流对象的onClose()方法,

但有几点需要注意:

  • onClose() 方法会返回流对象本身,也就是说可以对改对象进行多次调用
  • 如果调用了多个onClose() 方法,它会按照调用的顺序触发,但是如果某个方法有异常则只会向上抛出第一个异常
  • 前一个 onClose() 方法抛出了异常不会影响后续 onClose() 方法的使用
  • 如果多个 onClose() 方法都抛出异常,只展示第一个异常的堆栈,而其他异常会被压缩,只展示部分信息

5、并行流和串行流

BaseStream接口中分别提供了并行流和串行流两个方法,这两个方法可以任意调用若干次,也可以混合调用,但最终只会以最后一次方法调用的返回结果为准。

参考parallel()方法的说明:

Returns an equivalent stream that is parallel. May return

itself, either because the stream was already parallel, or because

the underlying stream state was modified to be parallel.

所以多次调用同样的方法并不会生成新的流,而是直接复用当前的流对象。

下面的例子里以最后一次调用parallel()为准,最终是并行地计算sum:

stream.parallel()   .filter(...)   .sequential()   .map(...)   .parallel()   .sum();

6、ParallelStream背后的男人:ForkJoinPool

ForkJoin框架是从JDK7中新特性,它同ThreadPoolExecutor一样,也实现了Executor和ExecutorService 接口。它使用了一个「无限队列」来保存需要执行的任务,而线程的数量则是通过构造函数传入, 如果没有向构造函数中传入希望的线程数量,那么当前计算机可用的CPU数量会被设置为线程数量作为默认值。

ForkJoinPool主要用来使用分治法(Divide-and-Conquer Algorithm) 来解决问题,典型的应用比如_快速排序算法_。这里的要点在于,ForkJoinPool需要使用相对少的线程来处理大量的任务。

比如要对1000万个数据进行排序,那么会将这个任务分割成两个500 万的排序任务和一个针对这两组500万数据的合并任务。

以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。那么到最后,所有的任务加起来会有大概2000000+个。

问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行,想象一下归并排序的过程。

所以当使用ThreadPoolExecutor时,使用分治法会存在问题,因为ThreadPoolExecutor中的线程无法向 任务队列中再添加一个任务并且在等待该任务完成之后再继续执行。而使用ForkJoinPool时,就能够让其中的线程创建新的任务,并挂起当前的任务,此时线程就能够从队列中选择子任务执行。

那么使用ThreadPoolExecutor或者ForkJoinPool,会有什么性能的差异呢?

首先,使用ForkJoinPool能够使用数量有限的线程来完成非常多的具有「父子关系」的任务,比如使用4个线程来完成超过200万个任务。使用ThreadPoolExecutor 时,是不可能完成的,因为ThreadPoolExecutor中的Thread无法选择优先执行子任务,需要完成200万个具有父子关系的任务时,也需要200万个线程,显然这是不可行的。

Work Stealing原理:

  • 每个工作线程都有自己的工作队列WorkQueue
  • 这是一个双端队列dequeue,它是线程私有的;
  • ForkJoinTask中fork的子任务,将放入运行该任务的工作线程的队头,工作线程将以LIFO的顺序来处理工作队列中的任务,即堆栈的方式;
  • 为了最大化地利用CPU,空闲的线程将从其它线程的队列中「窃取」任务来执行
  • 但是是从工作队列的尾部窃取任务,以减少和队列所属线程之间的竞争;
  • 双端队列的操作:push()/pop()仅在其所有者工作线程中调用,poll()是由其它线程窃取任务时调用的;
  • 当只剩下最后一个任务时,还是会存在竞争,是通过CAS来实现的;

7、用ForkJoinPool的眼光来看ParallelStream

Java 8为ForkJoinPool添加了一个通用线程池,这个线程池用来处理那些没有被显式提交到任何线程池的任务。它是ForkJoinPool类型上的一个静态元素,它拥有的默认线程数量等于运行计算机上的CPU数量。

当调用Arrays 类上添加的新方法时,自动并行化就会发生。

比如用来排序一个数组的并行快速排序,用来对一个数组中的元素进行并行遍历。自动并行化也被运用在Java 8新添加的Stream API中。

比如下面的代码用来遍历列表中的元素并执行需要的操作:

List<UserInfo> userInfoList =        DaoContainers.getUserInfoDAO().queryAllByList(new UserInfoModel());userInfoList.parallelStream().forEach(RedisUserApi::setUserIdUserInfo);

对于列表中的元素的操作都会以并行的方式执行。forEach方法会为每个元素的计算操作创建一个任务,该任务会被前文中提到的ForkJoinPool中的commonPool处理。

以上的并行计算逻辑当然也可以使用ThreadPoolExecutor完成,但是就代码的可读性和代码量而言,使用ForkJoinPool明显更胜一筹。

对于ForkJoinPool通用线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量。也可以通过设置系统属性: -Djava.util.concurrent .ForkJoinPool.common.parallelism=N (N为线程数量),来调整ForkJoinPool的线程数量。

值得注意的是:当前执行的线程也会被用来执行任务,所以最终的线程个数为N+1,1就是当前的主线程。

这里就有一个问题,如果你在并行流的执行计算使用了_阻塞操作_,如I/O,那么很可能会导致一些问题:

public static String query(String question) {  List<String> engines = new ArrayList<String>();  engines.add("http://www.google.com/?q=");  engines.add("http://duckduckgo.com/?q=");  engines.add("http://www.bing.com/search?q=");  // get element as soon as it is available  Optional<String> result = engines.stream().parallel().map((base) - {    String url = base + question;    // open connection and fetch the result    return WS.url(url).get();  }).findAny();  return result.get();}

这个例子很典型,让我们来分析一下:

这个并行流计算操作将由主线程和JVM默认的ForkJoinPool.commonPool()来共同执行。

map中是一个阻塞方法,需要通过访问HTTP接口并得到它的response,所以任何一个worker线程在执行到这里的时候都会阻塞并等待结果。

所以当此时再其他地方通过并行流方式调用计算方法的时候,将会受到此处阻塞等待的方法的影响。

目前的ForkJoinPool的实现并未考虑补偿等待那些阻塞在等待新生成的线程的工作worker线程,所以最终ForkJoinPool.commonPool()中的线程将备用光并且阻塞等待。

 “

正如我们上面那个列子的情况分析得知,lambda的执行并不是瞬间完成的,所有使用parallel streams的程序都有可能成为阻塞程序的源头, 并且在执行过程中程序中的其他部分将无法访问这些workers,这意味着任何依赖parallel streams的程序在什么别的东西占用着common ForkJoinPool时将会变得不可预知并且暗藏危机。

小结:

  • 当需要处理递归分治算法时,考虑使用ForkJoinPool
  • 仔细设置不再进行任务划分的阈值,这个阈值对性能有影响。
  • Java 8中的一些特性会使用到ForkJoinPool中的通用线程池。在某些场合下,需要调整该线程池的默认的线程数量
  • lambda应该尽量避免副作用,也就是说,避免突变基于堆的状态以及任何IO
  • lambda应该互不干扰,也就是说避免修改数据源(因为这可能带来线程安全的问题)
  • 避免访问在流操作生命周期内可能会改变的状态

8、并行流的性能

并行流框架的性能受以下因素影响:

  • 数据大小:数据够大,每个管道处理时间够长,并行才有意义;
  • 源数据结构:每个管道操作都是基于初始数据源,通常是集合,将不同的集合数据源分割会有一定消耗;
  • 装箱:处理基本类型比装箱类型要快;
  • 核的数量:默认情况下,核数量越多,底层fork/join线程池启动线程就越多;
  • 单元处理开销:花在流中每个元素身上的时间越长,并行操作带来的性能提升越明显;

源数据结构分为以下3组:

  • 性能好:ArrayList、数组或IntStream.range(数据支持随机读取,能轻易地被任意分割)
  • 性能一般:HashSetTreeSet(数据不易公平地分解,大部分也是可以的)
  • 性能差:LinkedList(需要遍历链表,难以对半分解)、Stream.iterateBufferedReader.lines(长度未知,难以分解)

注意:下面几个部分节选自:Streams 的幕后原理,顺便感谢一下作者_Brian Goetz_,写的太通透了。

9、NQ模型

要确定并行性是否会带来提速,需要考虑的最后两个因素是:可用的数据量和针对每个数据元素执行的计算量。

在我们最初的并行分解描述中,我们采用的概念是拆分来源,直到分段足够小,以致解决该分段上的问题的顺序方法更高效。分段大小必须依赖于所解决的问题,确切的讲,取决于每个元素完成的工作量。

例如,计算一个字符串的长度涉及的工作比计算字符串的 SHA-1 哈希值要少得多。为每个元素完成的工作越多,“大到足够利用并行性” 的阈值就越低。类似地,拥有的数据越多, 拆分的分段就越多,而不会与 “太小” 阈值发生冲突。

一个简单但有用的并行性能模型是 NQ 模型,其中 N 是数据元素数量,Q 是为每个元素执行的工作量。乘积 N*Q 越大,就越有可能获得并行提速。对于具有很小的 Q 的问题,比如对数字求和,您通常可能希望看到 N > 10,000 以获得提速;随着 Q 增加,获得提速所需的数据大小将会减小。

并行化的许多阻碍(比如拆分成本、组合成本或遇到顺序敏感性)都可以通过 Q 更高的操作来缓解。尽管拆分某个 LinkedList 特征的结果可能很糟糕,但只要拥有足够大的 Q,仍然可能获得并行提速。

10、遇到顺序

遇到顺序指的是来源分发元素的顺序是否对计算至关重要。一些来源(比如基于哈希的集合和映射)没有有意义的遇到顺序。流标志 ORDERED 描述了流是否有有意义的遇到顺序。

JDK 集合的 spliterator 会根据集合的规范来设置此标志;

一些中间操作可能注入 ORDERED (sorted()) 或清除它 (unordered())。

如果流没有遇到顺序,大部分流操作都必须遵守该顺序。对于顺序执行,会「自动保留遇到顺序」,因为元素会按遇到它们的顺序自然地处理。

甚至在并行执行中,许多操作(无状态中间操作和一些终止操作(比如 reduce())),遵守遇到顺序不会产生任何实际成本。

但对于其他操作(有状态中间操作,其语义与遇到顺序关联的终止操作,比如 findFirst() forEachOrdered()) , 在并行执行中遵守遇到顺序的责任可能很重大。

如果流有一个已定义的遇到顺序,但该顺序对结果没有意义, 那么可以通过使用 unordered() 操作删除 ORDERED 标志,加速包含顺序敏感型操作的管道的顺序执行。

作为对遇到顺序敏感的操作的示例,可以考虑 limit(),它会在指定大小处截断一个流。在顺序执行中实现 limit() 很简单:保留一个已看到多少元素的计数器,在这之后丢弃任何元素。

但是在并行执行中,实现 limit() 要复杂得多;您需要保留前 N 个元素。此要求大大限制了利用并行性的能力;如果输入划分为多个部分,您只有在某个部分之前的所有部分都已完成后,才知道该部分的结果是否将包含在最终结果中。

因此,该实现一般会错误地选择不使用所有可用的核心,或者缓存整个试验性结果,直到您达到目标长度。

如果流没有遇到顺序,limit() 操作可以自由选择任何 N 个元素,这让执行效率变得高得多。知道元素后可立即将其发往下游, 无需任何缓存,而且线程之间唯一需要执行的协调是发送一个信号来确保未超出目标流长度。

遇到顺序成本的另一个不太常见的示例是排序。如果遇到顺序有意义,那么 sorted() 操作会实现一种稳定 排序 (相同的元素按照它们进入输入时的相同顺序出现在输出中),而对于无序的流,稳定性(具有成本)不是必需的。

distinct() 具有类似的情况:如果流有一个遇到顺序,那么对于多个相同的输入元素,distinct() 必须发出其中的第一个, 而对于无序的流,它可以发出任何元素 — 同样可以获得高效得多的并行实现。

在使用 collect() 聚合时会遇到类似的情形。如果在无序流上执行 collect(groupingBy()) 操作, 与任何键对应的元素都必须按它们在输入中出现的顺序提供给下游收集器。

此顺序对应用程序通常没有什么意义,而且任何顺序都没有意义。在这些情况下,可能最好选择一个并发 收集器(比如 groupingByConcurrent() ),它可以忽略遇到顺序, 并让所有线程直接收集到一个共享的并发数据结构中(比如 ConcurrentHashMap),而不是让每个线程收集到它自己的中间映射中, 然后再合并中间映射(这可能产生很高的成本)。

到此这篇关于Java 8 Stream流强大的原理的文章就介绍到这了,更多相关Java 8 Stream流内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于Java8并行流(parallelStream)的注意点

    Java8并行流(parallelStream)注意点 在最初使用并行流的时候,查询列表会偶尔性报空指针异常,这令我非常纳闷 代码是这样的: List<OrderListVO> orderListVOS = new LinkedList<OrderListVO>(); baseOrderBillList.parallelStream().forEach(baseOrderBill -> { OrderListVO orderListVO = new OrderListVO()

  • java8新特性之stream流中reduce()求和知识总结

    1.stream().reduce()单字段求和 (1)普通数字求和 public static void test2(){ List<Integer> list= Arrays.asList(new Integer[]{1,2,3,4,5,6,7,8,9}); Integer sum=list.stream().reduce((x,y)->x+y).get(); System.out.println(sum); } 2.BigDecimal求和 public static void m

  • Java8新特性Stream流实例详解

    什么是Stream流? Stream流是数据渠道,用于操作数据源(集合.数组等)所生成的元素序列. Stream的优点:声明性,可复合,可并行.这三个特性使得stream操作更简洁,更灵活,更高效. Stream的操作有两个特点:可以多个操作链接起来运行,内部迭代. Stream可分为并行流与串行流,Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换.串行流就不必再细说了,并行流主要是为了为了适应目前多核机器的时代,提高系统CP

  • 详解java8中的Stream数据流

    Stream是java8引入的一个重度使用lambda表达式的API.Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象.直观意味着开发者在写代码时只需关注他们想要的结果是什么而无需关注实现结果的具体方式.这一章节中,我们将介绍为什么我们需要一种新的数据处理API.Collection和Stream的不同之处以及如何将StreamAPI应用到我们的编码中. 筛选重复的元素 Stream 接口支持 distinct 的方法, 它会返回一个元素

  • Java8中的Stream流式操作教程之王者归来

    前言 相对于Java8之前的Java的相关操作简直是天差地别,Java8 的流式操作的出现,也很大程度上改变了开发者对于Java的繁琐的操作的印象,从此,Java也走向了函数式编程的道路! 1 流的创建 1.1 流的创建方法 既然需要聊聊流的操作,那么,首先还是先看看怎么创建流. 创建流的方法有三种,分别是:Stream.of().Stream.iterate().Stream.generate(),然后,分别看一下这三个方法的声明. static <T> Stream<T> of

  • Java 8 Stream流强大的原理

    目录 1.Stream的组成与特点 2.BaseStream接口 3.Stream接口 4.关闭流操作 5.并行流和串行流 6.ParallelStream背后的男人:ForkJoinPool 7.用ForkJoinPool的眼光来看ParallelStream 8.并行流的性能 9.NQ模型 10.遇到顺序 前言: Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象. Stream API可以极大提高Java程序员的生产力,让程

  • Java中Stream流去除List重复元素的方法

    本文实例为大家分享了Java中Stream流去除List重复元素的具体代码,供大家参考,具体内容如下 业务场景 在开发中我们常常需要过滤List中的重复对象,而重复的定义往往是根据单个条件或者多个条件,如果是单个条件的话还是比较好处理的,即使不使用工具,代码也可以很容易实现,但如果判断依据不是单个条件,而是多个条件的话,代码实现起来就会比较复杂,此时我们一般就会使用工具来简化开发 单条件去重代码 ArrayList<listData> collect = list.stream().colle

  • Java中Stream流中map和forEach的区别详解

    目录 什么是 stream 流 Map forEach 使用场景 不是很难的知识,但是今天犯错了,记录一下 什么是 stream 流 我们在使用集合或数组对元素进行操作时往往会遇到这种情况:通过对不同类型的存储元素,按照特定条件进行查找.排序.等操作时往往会写一大段代码,而且更要命的是,不同类型的数据,操作的方法也不一样,比如一个存储 Student 实体类和一个只存储 String 类型的集合俩者的操作步骤肯定大不一样且无法通用,而 stream API 就解决了这些问题,对数据操作时进行了统

  • 一文详解Java中Stream流的使用

    目录 简介 操作1:创建流 操作2:中间操作 筛选(过滤).去重 映射 排序 消费 操作3:终止操作 匹配.最值.个数 收集 规约 简介 说明 本文用实例介绍stream的使用. JDK8新增了Stream(流操作) 处理集合的数据,可执行查找.过滤和映射数据等操作. 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询.可以使用 Stream API 来并行执行操作. 简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式. 特点 不是数据结构

  • Java集合Stream流操作的基本使用教程分享

    目录 事前准备 Filter Sorted Map Match Count Reduce 总结 Java 中可以使用 java.util.Stream 对一个集合(实现了java.util.Collection接口的类)做各种操作,例如:求和.过滤.排序等等. 这些操作可能是中间操作——返回一个 Stream 流,或者是终端操作——返回一个结果. 流操作并不会影响原来的集合,可以简单认为,流操作是把集合中的一个元素逐个复制放到一个首尾相接的流动的水槽中. Stream 流支持同步执行,也支持并发

  • 关于JAVA中stream流的基础处理(获取对象字段和对象批量处理等)

    目录 Stream流程处理情况 1:按指定字段获取集合对象中的字段: 2:按指定字段对集合对象进行排序: 3: 按指定字段对集合对象去重处理 4: 对集合对象批量处理 5: 将集合对象中指定字段转数组 Stream流程处理情况 1:按指定字段获取集合对象中的字段: List<String> idList = initUserList.stream().map(User::getId).collect(Collectors.toList()); 2:按指定字段对集合对象进行排序: List<

  • Java的Stream流来了解一下

    目录 Stream流 1.什么是Stream流: 2.创建流: 3.Stream的map映射流 4.Stream查找与匹配 总结 Stream流 上篇文章讲了Java 8 的一个新特性:Lambda表达式,在业务中若能熟练的使用,可以节省很多代码量,看着也整洁很多.那么这篇文章将介绍另一个新特性:Stream流,不要看错哈!!!不是打游戏的steam!! 1.什么是Stream流: Stream 是Java 8 提出的一个新概念,不是输入输出的 Stream 流 (和IO流其实没有任何关系哈),

  • Java IO字符流缓冲区实现原理解析

    字符流的缓冲区 缓冲区的出现,提高了对数据的读写效率,对应的类:BufferedWriter,BufferedReader 缓冲区要结合流才可以使用,缓冲区是在流的基础上对流的功能进行增强 BufferedWriter 将文本写入到字符输出流中,缓冲字符,以便提供对单个字符.数组和字符串的有效写入. 可以指定缓冲区大小,也可以接受默认大小.默认是足够大的用于大多数目的. 提供了一种newline()方法,利用平台自身观念的行分隔符由系统性line.separator定义.并不是所有的平台都使用换

  • Java关于JDK1.8新特性的Stream流

    目录 Java 的Stream流 一.定义 二.操作的特征 三.代码示例 1.生成流 2.forEach 迭代 3.limit方法用于获取指定数量的流 4.map 5.sorted 6.并行(parallel)程序 7.Collectors 8.转化(将枚举类转成map) Java 的Stream流 一.定义 JDK1.8 中增加了Stream流,Stream流是一个来自数据源的元素队列并支持聚合操作.元素是特定类型的对象,形成一个队列,Java中的Stream并不会存储元素,而是按需计算数据源

  • Java基础之Stream流原理与用法详解

    目录 一.接口设计 二.创建操作 三.中间操作 四.最终操作 五.Collect收集 Stream简化元素计算 一.接口设计 从Java1.8开始提出了Stream流的概念,侧重对于源数据计算能力的封装,并且支持序列与并行两种操作方式:依旧先看核心接口的设计: BaseStream:基础接口,声明了流管理的核心方法: Stream:核心接口,声明了流操作的核心方法,其他接口为指定类型的适配: 基础案例:通过指定元素的值,返回一个序列流,元素的内容是字符串,并转换为Long类型,最终计算求和结果并

随机推荐