Java8-Stream

Stream流

Stream流是Java API的新成员,它允许声明性方式处理数据集合,可以把它们看成遍历数据集的高级迭代器。
通常在集合的相关操作中,一般有存储、迭代、计算三个方面的功能,在Java8之前一般是用集合进行存储,用迭代器进行外部迭代和计算,而Stream的引入,是将计算的功能拆分出来。

集合和流的区别

粗略地说,集合与流之间的差异就在于什么时候进行计算。集合使用的时候所有元素都在内存中,流使用的时候才会出现。从另一个角度来说,流就像是一个延迟创建的集合

  • 集合是一个内存中的数据结构,它包含数据结构中目前所有的值——集合中的每个元素都得先算出来才能添加到集合中
  • 流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计算的。只会按需生成
  • 外部迭代与内部迭代。Streams库的内部迭代可以自动选择一种适合你硬件的数据表示和并行实现;外部迭代需要自己进行处理

相关概念

元素序列:就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值
源:流会使用一个提供数据的源,如集合、数组或输入/输出资源。请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执行,也可并行执行。
流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线
内部迭代:与集合使用迭代器显式迭代的集合不同,流的迭代操作是内部进行的,我们不需要去进行迭代控制

Stream优点

  • 声明性。代码是以声明性方式写的,更简洁,更易读
  • 可复合。你可以把几个基础操作链接起来,来表达复杂的数据处理流水线。同时保持代码清晰可读
  • 可并行。因为filter、sorted、map和collect等操作是与具体线程模型无关的高层次构件,所以它们的内部实现可以是单线程的,也可能透明地充分利用你的多核架构

构建流

  • 集合创建流
List<String> list = new ArrayList<>();
list.stream();
  • 值创建流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
  • 数组创建流
int[] numbers = {2, 3, 5, 7, 11, 13};
Arrays.stream(numbers)
  • 文件生成流。使用Files.lines得到一个流,其中的每个元素都是给定文件中的一行
Stream<String> stream = Files.lines(Paths.get("test.txt"));
  • 由函数生成流。Stream.iterate和Stream.generate。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流
Stream.iterate(1, n->n+1).limit(10).forEach(System.out::println);

使用流的常见方法

Stream类中的方法分为中间操作和终端操作。中间操作是用来组装流水线,终端操作是使用流水线,消耗元素并产生结果。

中间操作

除非流水线上触发一个终端操作,否则中间操作不会执行任何处理

  • filter。接受Lambda,从流中排除某些元素
  • map。接受一个Lambda,将元素转换成其他形式或提取信息
  • mapToInt、mapToLong等。避免装箱的map操作
  • limit。截断流,使其元素不超过给定数量。
  • distinct。返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流
  • skip返回一个扔掉了前n个元素的流。
  • flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
  • iterate传入初始值和迭代方法创建无限流,可以通过limit进行长度限制
  • peek。接收一个Lambda,为每个元素执行函数操作
  • sorted。为流按照默认顺序或者接收Lambda进行排序
终端操作

终端操作会从流的流水线生成结果

  • collect。将流转换为其他形式。
  • 查找和匹配:allMatch、anyMatch、noneMatch、findFirst和findAny
  • anyMatch.anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”
  • allMatch.流中的元素是否都能匹配给定的谓语
  • noneMatch.确保流中没有任何元素与给定的谓词匹配
  • findAny方法将返回当前流中的任意元素
  • reduce.将流中所有元素反复结合起来,得到一个值
  • max、min、sum、count

收集器

我们对集合的遍历操作,要么是为每个元素执行特定方法,要么是消费掉元素产生特定类型的结果。在产生结果的时候一般采用collect方法,它接收一个Collector对象,里面定义了如何对数据进行归约操作。

预定义收集器

Collectors中提供了很多有用的预定义收集器,它们分为下面三大类

  • 将流元素归约和汇总为一个值
  • 元素分组
  • 元素分区
常见的预定义收集器
  • counting
  • maxBy、minBy
  • averagingInt、summingInt、
  • summarizingInt
  • joining
  • reducing
  • groupingBy
  • collectingAndThen
  • partitioningBy
收集器接口

Collector接口包含了一系列方法,为实现具体的归约操作(即收集器)提供了范本

// T是流中要收集的项目的泛型。
// A是累加器的类型,累加器是在收集过程中用于累积部分结果的对象。
// R是收集操作得到的对象(通常但并不一定是集合)的类型。
// public class ToListCollector<T> implements Collector<T, List<T>, List<T>>
public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner();
    Function<A, R> finisher();
    Set<Characteristics> characteristics();
    ...
  • supplier方法必须返回一个结果为空的Supplier,也就是一个无参数函数,在调用时它会创建一个空的累加器实例,供数据收集过程使用
  • accumulator方法会返回执行归约操作的函数
  • 在遍历完流后,finisher方法必须返回在累积过程的最后要调用的一个函数,以便将累加器对象转换为整个集合操作的最终结果
  • combiner方法会返回一个供归约操作使用的函数,它定义了对流的各个子部分进行并行处理时,各个子部分归约所得的累加器要如何合并
  • characteristics会返回一个不可变的Characteristics集合,它定义了收集器的行为——尤其是关于流是否可以并行归约,以及可以使用哪些优化的提示

Stream并行

本部分简单介绍了Stream并行的部分内容,实际使用需要经过大量测试。
Stream针对并行操作提供了解决思路,因为是内部迭代,它的并行实现不需要我们去进行控制,因此实现起来非常简单。但是针对Stream的并行最好进行大量的测试,因为Stream中并不是所有的操作都适合并行,不适当的操作甚至不如顺序Stream流。
并行流内部使用默认的ForkJoinPool,它默认的线程数量就是处理器的数量。

System.out.println(Runtime.getRuntime().availableProcessors());
// 可以通过下面方法改变线程池大小,它将影响代码中所有的并行流
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
并行流的相关方法
  • parallelStream方法将集合转换为并行流
  • parallel可以将顺序流转变成并行流
  • sequential可以将并行流转变成顺序流
针对并行计算的优化思路

在大量的循环中,首先尽量避免大量的装箱操作,比如calBySeq和calByLongStreamSeq;
合理的选择并行流的实现方式

package test;

import java.util.function.Function;
import java.util.stream.LongStream;
import java.util.stream.Stream;

public class Test {

	public static void main(String... args) {
		System.out.println("执行时间为" + measureSumPerf(Test::calByLongStreamPal, 10000000) + "毫秒");
	}

	public static long measureSumPerf(Function<Long, Long> adder, long n) {
		long fastest = Long.MAX_VALUE;
		for (int i = 0; i < 10; i++) {
			long start = System.nanoTime();
			long sum = adder.apply(n);
			long duration = (System.nanoTime() - start) / 1_000_000;
			System.out.println("Result: " + sum);
			if (duration < fastest)
				fastest = duration;
		}
		return fastest;
	}
	
	// 执行时间为85毫秒
	public static long calBySeq(long n) {
		return Stream.iterate(1L, i->i+1).limit(n).reduce(1L, Long::sum);
	}
	
	// 执行时间为142毫秒
	public static long calByPal(long n) {
		return Stream.iterate(1L, i->i+1).limit(n).parallel().reduce(1L, Long::sum);
	}
	
	// 执行时间为4毫秒;普通的迭代器方式,非并行
	public static long calByIte(long n) {
		long a =0;
		for (long i=1;i<=n;i++) {
			a += i;
		}
		return a;
	}
	
	// 执行时间为4毫秒
	public static long calByLongStreamSeq(long n) {
		return LongStream.range(1, n).reduce(1L, Long::sum);
	}
	
	// 执行时间为1毫秒
	public static long calByLongStreamPal(long n) {
		return LongStream.range(1, n).parallel().reduce(1L, Long::sum);
	}

}

并行流注意要点
  • 并行流并不总比顺序流快,要通过测试来校验
  • 注意装箱带来的性能影响
  • 注意操作,有些操作本身在并行流上就比顺序流差
  • 要考虑流背后的数据结构是否容易分解,ArrayList比LinkedList分解要容易得多
    [外链图片转存失败(img-lPq855U5-1562652278989)(31BB9B652A554E1191695F533AE8B743)]
  • 还要考虑终端操作中合并步骤的代价是大是小
  • 保证在内核中并行执行工作的时间比在内核之间传输数据的时间长
新增Spliterator接口-可分迭代

和Iterator一样,Spliterator也用于遍历数据源中的元素,但它是为了并行执行而设计的。Java8已经为集合框架中包含的所有数据结构提供了一个默认的Spliterator实现。在实际使用中,可以将Spliterator与StreamSupport结合创建并行流Stream,由Spliterator来控制并行时拆分数据的策略。

public interface Spliterator<T> {
    // 尝试对T执行函数
    boolean tryAdvance(Consumer<? super T> action);
    // 为每个T执行函数
    default void forEachRemaining(Consumer<? super T> action) {
        do { } while (tryAdvance(action));
    }
    // 对Spliterator进行拆分,不能拆分返回null
    Spliterator<T> trySplit();
    // 返回大致剩余元素数
    long estimateSize();
    // Spliterator本身特性集的编码
    int characteristics();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
后台采用apache服务器下的cgi处理c语言做微信小程序后台逻辑的脚本映射。PC端的服务器和客户端都是基于c语言写的。采用mysql数据库进行用户数据和聊天记录的存储。.zip C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块化。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值