记录生活中的点点滴滴

0%

Java8的流库

在处理集合时,当我们需要遍历集合中元素,或者需要在每个元素时进行操作。列如,假设我们想要对某本书中的所有长单词进行计数。可以这样,先得到这个txt文件的字符串,然后通过正则表达式把它分割成单词放入List集合中,然后设定count变量,遍历List,如果单词长度大于12,count就加1。代码如下:

1
2
3
4
5
6
7
8
9
10
11
//传统思想
@Test
public void Test1() throws IOException {
String contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
List<String> words = List.of(contents.split("\\PL+"));//通过正则表达式将其单词分成一个个字符
long count = 0;
for (String w : words) {
if(w.length()>12) count++;
}
System.out.println(count);
}

在使用流之后,相同的操作看起来像下面这样:

1
2
3
4
5
6
7
8
9
//使用流之后
@Test
public void Test2() throws IOException {
String contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
List<String> words = List.of(contents.split("\\PL+"));//通过正则表达式将其单词分成一个个字符
// long count = words.stream().filter(w->w.length()>12).count();//将list转成流进行操作
long count = words.parallelStream().filter(w->w.length()>12).count();//将stream转换成parallelStream可以以并行的方式执行
System.out.println(count);
}

新的操作可读性很强,可以直接通过方法名确定这段代码意欲何为。此外,将 stream() 修改为 parallelStream() 可以并行的方式来执行过滤和计数。

流是什么

流是一类数据项,是一种可以让我们在更高的概念级别上指定计算任务的数据视图。

流的优点

流遵循了“做什么而非怎么做”的原则,它主要是对集合进行处理,它与集合相似,却有着显著的差异。

1,流并不储存元素。这些元素可能储存在底层的集合中,或者是按需生成的。

2,流的操作不会修改其数据源。当需要对数据进行处理时,会生成一个新的流包含修改后的元素。

3,流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。

流的创建

通过Collection接口的stream方法将集合转化为一个流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//1.通过Collection接口的stream方法将集合转化为一个流
@Test
public void Test1() throws IOException {
// default Stream<E> stream()
// default Stream<E> parallelStream()
// 产生当前集合中所有元素的顺序流或并行流
String contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
List<String> words = List.of(contents.split("\\PL+"));
Stream<String> stream1 = words.stream();
Stream<String> stream2 = words.stream();

//还可以通过Array.stream(array,from,to)可以用数组中的一部分元素来创建一个流
int[] arr = new int[]{1,2,3,4};
IntStream stream = Arrays.stream(arr, 0, 2);
}

通过静态的Stream.of方法

1
2
3
4
5
6
7
8
//2.通过静态的Stream.of方法
@Test
public void Test2() throws IOException {
String contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
Stream<String> stream = Stream.of(contents.split("\\PL+"));
//of方法具有可变长参数,因此我们可以构建具有任意数量引元的流;
Stream<String> stream1 = Stream.of("one", "two", "three", "four");
}

通过Stream.empty方法

1
2
3
4
5
//3.通过Stream.empty方法
@Test
public void Test3(){
Stream<Object> emptyStream = Stream.empty();
}

通过Stream.generate方法

1
2
3
4
5
6
7
8
9
//4.通过Stream.generate方法
@Test
public void Test4(){
//static <T> Stream<T> generate(Supplier<T> s)
//产生一个无限流,它的值是通过反复调用函数s而构建的
Stream<String> strem1 = Stream.generate(() -> "hello");
Stream<Double> strem2 = Stream.generate(Math::random);
//generate方法接受一个不包含任何引元的函数(及java8 函数式接口 Supplier<T>接口的对象)
}

通过Stream.iterate方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//5.通过Stream.iterate方法
@Test
public void Test5(){
//static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
//static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> f)
//产生一个无限流,他的元素包含 seed,在 seed上调用f产生的值,在前一个元素上调用f产生的值,等等。
// 第一个方法会产生一个无限流,而第二个方法产生的流会在碰到第一个不满足hasNext的元素时终止。

//该序列中的一个元素时种子BigInteger.ZERO,第二个元素时f(seed),即1,下一个元素时f(f(seed)),即2,后续以此类推。
Stream<BigInteger> integerStream = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
//下面限制了最大值,limit为n的最大值
var limit = new BigInteger("100000");
Stream<BigInteger> stream = Stream.iterate(BigInteger.ZERO, n -> n.compareTo(limit) < 0, n -> n.add(BigInteger.ONE));
}

通过Stream.ofNullable方法

1
2
3
4
5
6
7
8
9
//6.通过Stream.ofNullable方法
@Test
public void Test6() throws IOException {
//static <T> Stream<T> ofNummable(T t)
//如果t为null,返回一个空流,否则返回包含t的流。
var contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
Stream<String> stream1 = Stream.ofNullable(contents);
Stream<String> stream2 = Stream.ofNullable(null);
}

通过迭代器Spliterator的spliteratorUnknownSize方法

1
2
3
4
5
6
7
8
9
//7.通过迭代器Spliterator的spliteratorUnknownSize方法
@Test
public void Test7(){
//static <T> Spliterator<T> spliteratorUnknownSize(Iterator<? extends T> iterator, int characteristics)
//用于给定的特性(一种包含诸如Spliterator.ORDERED之类的常量的位模式)将一个迭代器转换为一个具有未知尺寸可分割的迭代器。
Iterator<Path> iterator = Paths.get("words.txt").iterator();
Stream<Path> stream = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
}

通过regex包下Pattern的splitAsStream方法

1
2
3
4
5
6
7
8
//8.通过regex包下Pattern的splitAsStream方法
@Test
public void Test8() throws IOException {
//Stream<String> splitAsStream(CharSequence input)
//产生一个流,它的元素是input中的由compile方法通过正则表达式界定的部分
var contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
Stream<String> stream = Pattern.compile("\\PL+").splitAsStream(contents);
}

通过Files.lines方法

1
2
3
4
5
6
7
8
9
10
//9.通过Files.lines方法
@Test
public void Test9() throws IOException {
//static Stream<String> lines(Path path)
//static Stream<String> lines(Path path, Charset cs)
//产生一个流,它的元素时指定文件中的行,该文件的字符集为UTF-8,或者为指定的字符集。
Path path = Paths.get("words.txt");
Stream<Path> stream = Files.list(path);
Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8);
}

通过StreamSupport.stream方法

1
2
3
4
5
6
7
8
//10.通过StreamSupport.stream方法
@Test
public void Test10(){
//static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel)
//产生一个流,它包含了由给定的可分割迭代器产生的值。
Iterable<Path> iterable = FileSystems.getDefault().getRootDirectories();
Stream<Path> stream = StreamSupport.stream(iterable.spliterator(), false);
}

通过Scanner.stream方法

1
2
3
4
5
6
7
8
//11.通过Scanner.stream方法
@Test
public void Test11(){
//public Stream<String> tokens()
//产生一个字符串流,该字符串是调用这个扫描器的next方法时返回的。
Scanner scanner = new Scanner(System.in);
Stream<String> stream = scanner.tokens();
}

流的转换

流的转换会产生一个新的流,它的元素派生自另一个流中的元素。

通过Stream类自带的filter,map,flatMap方法

filter方法

1
2
3
4
5
6
7
8
@Test
public void FilterTest() throws IOException {
//Stream<T> filter(Predicate<? super T> predicate)
//产生一个流,它包含当前流中所有满足谓词条件的元素
var contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
Stream<String> words = Stream.of(contents.split("\\PL+"));
Stream<String> stream = words.filter(w -> w.length() > 12);
}

map方法

1
2
3
4
5
6
7
8
@Test
public void MapTest() throws IOException {
//<R> Stream<R> map(Function<? super T,? extends Stream<? extends R>>mapper)
//产生一个流,它包含将mapper应用于当前流中所有元素所产生的结果。
var contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
Stream<String> words = Stream.of(contents.split("\\PL+"));
Stream<String> stream = words.map(String::toLowerCase);
}

flatMap方法

1
2
3
4
5
6
7
8
@Test
public void FlatMapTest() throws IOException {
//<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
//产生一个流,它是通过将mapper应用于当前流中所有元素所参数的结果连接到一起获得的。(注意,这里的每个结果都是一个流)
var contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
Stream<String> words = Stream.of(contents.split("\\PL+"));
Stream<String> stream = words.flatMap(w->Stream.of(w.toLowerCase()));
}

小测试

对于filter和map方法,我们来进行一个小测试:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void Test1(){
//每次创建的stream只能使用一次,如果连续执行一个对象的stream 就会报错:
// java.lang.IllegalStateException: stream has already been operated upon or closed

Stream<String> stream1 = Stream.of("one", "two", "three");
// Stream<String> stream2 = stream1.filter(w -> w.length() > 3);
// stream2.forEach(w-> System.out.println(w));
Stream<String> stream3 = stream1.map(String::toUpperCase);
stream3.forEach(w-> System.out.println(w));
}

我们要注意的是,每次创建的stream只能使用一次,如果连续执行一个对象的stream 就会报错。

所以,我们自己创建的 stream1 流,要么我们接下来使用 filter 方法,要么使用 map 方法,不能两个都使用,会报异常。所以我们测试一个时候要把另外一个注释掉。

测试 filter 方法的结果:

测试 map 方法的结果:

可能我们现在对于 mapflatMap 两个方法还不太理解他们的区别,没事,我们接下来通过一个例子来说明。

我们先写一个函数,把一个字符串转换成流:

1
2
3
4
5
6
7
8
//例如,letters("hello")的返回值是流["h","e","l","l","o"]
public static Stream<String> letters(String s){
ArrayList<String> res = new ArrayList<>();
for (int i = 0; i < s.length(); i++) {
res.add(s.substring(i,i+1));
}
return res.stream();
}

我们把先创建一个流 Stream.of("hello","thanks","fine") ,想想我们怎样可以得到这几个单词对应字母的流,就是h,e,l,l,o,t,...

通过map方法,lambda表达式中每一个单词都传个 letters 方法,但是注意返回值的类型是 Stream<Stream<String>>两层Stream,所以后续我们遍历的时候要用两次lambda表达式,代码如下:

1
2
3
Stream<String> stream = Stream.of("hello","thanks","fine");
Stream<Stream<String>> stream1 = stream.map(w -> letters(w));
stream1.forEach(x-> x.forEach(xx-> System.out.printf("%s\t",xx)));

运行结果为:

这好像挺麻烦的,确实,如果我们用 flatMap 就超级简单,如下:

1
2
3
Stream<String> stream = Stream.of("hello","thanks","fine");
Stream<String> stream2 = stream.flatMap(w -> letters(w));
stream2.forEach(w-> System.out.printf("%s\t",w));

运行结果和上面一样。通过这个例子,我们应该就能差不多明白这两个的区别了。

抽取子流和组合流

如果你想对一个流进行裁剪,获取它的子流,或者将几个流组合在一起,可以通过以下方法去实现:

limit方法

1
2
3
4
5
6
7
8
//1.limit方法
@Test
public void Test1(){
//Stream<T> limit(long maxSize)
//产生一个流,其中包含了当前流中最初的maxSize个元素。
Stream<Double> stream = Stream.generate(Math::random).limit(100);
//产生一个包含100个随机数的流。
}

skip方法

1
2
3
4
5
6
7
8
//2.skip方法
@Test
public void Test2(){
//Stream<T> skip(long n)
//与limit正好相反,产生一个流,它的元素是当前流中除了前n个元素之外的所有元素。
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5).skip(2);
//stream中含有3 4 5
}

takeWhile方法

1
2
3
4
5
6
7
8
//3.takeWhile方法
@Test
public void Test3(){
//从Stream中依次获取满足条件的元素,直到不满足条件为止结束获取
//举例x -> x % 2 == 0 ,即判断是否为偶数,即当遇到元素不为偶数时终止获取
Stream.of(10,2,3,4,5,6).takeWhile(x->x%2==0).forEach(System.out::println);
//所以输出10 2,因为到3就是奇数了,结束迭代
}

dropWhile方法

1
2
3
4
5
6
7
//4.dropWhile方法
@Test
public void Test4(){
//从Stream中依次删除满足条件的元素,直到不满足条件为止结束删除
Stream.of(10,2,3,4,5,6).dropWhile(x->x%2==0).forEach(System.out::println);
//输出3 4 5 6
}

concat静态方法

1
2
3
4
5
6
7
//5.concat静态方法
@Test
public void Test5(){
//static <T> Stream<T> concat(Stream<? extends T>a, Stream<? extends T> b)
//产生一个流,它的元素是a的元素后面跟着b的元素。
Stream<String> stream = Stream.concat(Stream.of("one"), Stream.of("two"));
}

distinct方法

1
2
3
4
5
6
7
//6.distinct方法
@Test
public void Test6(){
//Stream<T> distinct()
//产生一个流,包含当前流中所有不同的元素,及剔除重复元素。
Stream<String> stream = Stream.of("hello", "world", "hello", "word").distinct();
}

sorted方法

1
2
3
4
5
6
7
8
9
10
//7.sorted方法
@Test
public void Test7(){
//Stream<T> sorted()
//Stream<T> sorted(Comparator<? super T> comparator)
//产生一个流,它的元素是当前流中所有元素按照顺序排列的。第一个方法要求元素是实现了Comparable的类的实例。
Stream<Integer> stream1 = Stream.of(5, 4, 1, 6, 3, 2).sorted();//123456
//下面输出654321
Stream.of(5, 4, 1, 6, 3, 2).sorted((o1, o2) -> o2.compareTo(o1)).forEach(System.out::println);
}

peek方法

1
2
3
4
5
6
7
8
9
10
11
12
13
//8.peek方法
@Test
public void Test8(){
//Java8提供了Stream,可以方便的进行一些数据操作,比如提供了过滤,分组甚至并行等特性。
//但是我们常常需要查看中间操作的结果,怎么办?
//用peek就能解决这个问题
Stream.of("one","two","three","four")
.filter(w->w.length()>3)
.peek(e-> System.out.println("Filtered value: "+e))
.map(String::toUpperCase)
.peek(e-> System.out.println("Mapped value:"+e))
.collect(Collectors.toList());
}

输出结果:

流的简单约简

在前面我们已经了解了流的创建和获取,以及对流本身的组合和截取。接下来,我们要了解的是从流当中获取答案,接下来所讨论的方法被称为约简约简是一种终结操作,它们通过将流约简为可以在程序中使用的非流值。

count方法

1
2
3
4
5
6
7
//1.count方法:获取当前流中元素的数量
@Test
public void Test1() throws IOException {
var contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
Stream<String> stream = Pattern.compile("\\PL+").splitAsStream(contents);
System.out.println(stream.count());//34
}

max和min方法

1
2
3
4
5
6
7
8
9
10
11
//2.max和min方法
@Test
public void Test2() throws IOException {
//Optional<T> max(Comparator<? super T> comparator)
//Optional<T> min(Comparator<? super T> comparator)
//分别产生该流的最大元素和最小元素,使用由给定比较器定义的排序规则,如果这个流为空,会产生一个空的Optional对象。
var contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
Stream<String> stream = Pattern.compile("\\PL+").splitAsStream(contents);
System.out.println(stream.max(String::compareToIgnoreCase));
// System.out.println(stream.min(String::compareToIgnoreCase));
}

findFirst和findAny方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//3.findFirst和findAny方法
@Test
public void Test3() throws IOException {
//Optional<T> findFirst()
//产生该流的第一个元素,如果流为空,会返回一个空的Optional对象。通常与filter组合使用。
var contents = new String(Files.readAllBytes(Paths.get("words.txt")), StandardCharsets.UTF_8);
Stream<String> words = Pattern.compile("\\PL+").splitAsStream(contents);
//找到流中第一个字母以S开头的单词
// Optional<String> start = words.filter(w -> w.startsWith("S")).findFirst();

//Optional<T> findAny()
//产生该流中任意一个元素,如果流为空,会返回一个空的Optional对象。在并行处理流时很有效。
Optional<String> s = words.parallel().filter(w -> w.startsWith("S")).findAny();
System.out.println(s);
}

anyMatch,allMatch,noneMatch方法

1
2
3
4
5
6
7
8
9
10
11
//4.anyMatch,allMatch,noneMatch方法
@Test
public void Test4(){
//boolean anyMatch(Predicate<? super T> predicate)
//boolean allMatch(Predicate<? super T> predicate)
//boolean noneMatch(Predicate<? super T> predicate)
//分别在这个流中任意元素,所有元素和没有任何元素匹配给定谓词时返回ture。
System.out.println(Stream.of("one", "two", "three").anyMatch(w -> w.startsWith("o")));//true
System.out.println(Stream.of("one", "two", "three").allMatch(w -> w.startsWith("o")));//false
System.out.println(Stream.of("one", "two", "three").noneMatch(w -> w.startsWith("o")));//false
}

Optional类

Optional对象是一种包装类对象,要么包含了类型为T的对象,要么没有包装任何对象。Optional类被当做一种更安全的方式,用来替代类型T的引用,这种引用要么引用某个对象,要么为null。接下来我们将讨论如何正确使用。

获取Optional值

1
2
3
4
5
6
7
8
9
10
11
12
13
//1.获取Optional值
@Test
public void Test1(){
//T orElse(T other)
//产生这个Optional的值,或者在该Optional为空时,产生other。
//T orElseGet(Supplier<? extends T> other)
//产生这个Optional的值,或者在该Optional为空时,产生调用other的结果。
//<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSuppier)
//产生这个Optional的值,或者在该Optional为空时,爆出调用exceptionSupplier的结果。
Optional<String> optional = Stream.of("one", "two", "three").filter(w -> w.startsWith("t")).findFirst();
System.out.println(optional.orElse(""));
optional.orElseThrow(IllegalStateException::new);
}

消费Optional值

1
2
3
4
5
6
7
8
9
10
11
12
//2.消费Optional值
@Test
public void Test2(){
//void ifPresent(Consumer<? super T> action)
// 如果该Optional不为空,就将它的值传递给action。
Optional<String> optional = Stream.of("one", "two", "three").filter(w -> w.startsWith("x")).findFirst();
optional.ifPresent(s-> System.out.println("value: "+s));

//void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
//如果该Optional不为空,就将它的值传给action,否则调用emptyAction。
optional.ifPresentOrElse(s-> System.out.println("value: "+s),()-> System.out.println("Not contains"));
}

管道化Optional值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//3.管道化Optional值
@Test
public void Test3(){
//<U> Optional<U> map<Function<? super T,? extends U> mapper>
//产生一个Optional,如果当前的Optional的值存在,那么所产生的Optional的值是通过将给定的函数应用于当前的Optional的值而得到的;
// 否则,产生一个空的Optional
Optional<String> optional = Stream.of("one", "two", "three").filter(w -> w.startsWith("x")).findAny();
optional.map(String::toUpperCase);

//Optional<T> filter(Predicate<? super T> predicate)
//产生一个Optional,如果当前的Optional的值满足给定的谓词条件,那么所产生的Optional的值就是当前Optional的值;
// 否则,产生一个空的Optional。
optional.filter(w->w.length()>5);

//Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
//如果当前Optional不为空,则产生当前的optional;否则由Supplier产生一个Optional。
optional.or(()->Stream.of("hello").findFirst());
}

不适用Optional值的方式

如果不正确的使用Optional类,那么相比以往的得到“某物或null” 的方式,你并没有得到任何好处。

下面试一些关于Optional类型正确的用法:

  • Optional类型的变量永远都不应该为null。
  • 不要使用Optional类型的域。因为其代价是额外多出来一个对象。在类的内部,使用null表示缺失的域更易于操作。
  • 不要在集合中放置Optional对象,并且不要将它们用作map的键。应该直接收集其中的值。

创建Optional值

1
2
3
4
5
6
7
8
9
10
11
12
//5.创建Optional值
@Test
public void Test5(){
//static <T> Optional<T> of(T value)
//static <T> Optional<T> ofNullable(T value)
//产生一个具有给定值的Optional。如果value为null,那么第一个方法会抛出一个NullPointerException异常,
// 而第二个方法会产生一个空Optional
//static <T> Optional<T> empty()
//产生一个空Optional。
Optional<Object> optional1 = Optional.empty();
Optional<Integer> optional2 = Optional.of(100);
}

用flatMap构建Optional值的方法

1
2
3
4
5
6
7
8
9
10
11
//6.用flatMap构建Optional值的方法
@Test
public void Test6(){
//<U> Optional<U> flatMap(Function<? super T,? extends Optional<? extends U>> mapper)
//如果Optional存在,产生将mapper应用于当前Optional值所产生的结果,或者在当前Optional为空时,返回一个空Optional。
Optional<Integer> u = Optional.of(100).flatMap(OptionalDemo::sqrt);
System.out.println(u.get());//10
}
public static Optional<Integer> sqrt(Integer x){
return x<0 ? Optional.empty():Optional.of((int)Math.sqrt(x));
}

访问结果

iterator方法

1
2
3
4
5
6
7
8
9
10
//1.iterator方法
@Test
public void Test1(){
//Iterator<T> iterator()
//产生一个用于获取当前流中各个元素的迭代器。这是一个终结操作。
Iterator<Integer> iterator = Stream.iterate(1, n -> n + 1).limit(10).iterator();
while (iterator.hasNext()){
System.out.printf("%d\t",iterator.next());
}
}

forEach方法

1
2
3
4
5
6
7
8
//2.forEach方法
@Test
public void Test2(){
//void forEach(Consumer<? super T> action)
//在流的每个元素上都调用action,这是一个终结操作。
Stream<Integer> stream = Stream.iterate(1, n -> n + 1).limit(10);
stream.forEach(x-> System.out.printf("%d\t",x));
}

toArray方法

1
2
3
4
5
6
7
8
9
10
//3.toArray方法
@Test
public void Test3(){
//Project[] toArray()
//<A> A[] toArray(IntFunction<A[]> generator)
//产生一个对象数组,或者在将引用A[]::new传递给构造器时,返回一个A类型的数组。
Stream<Integer> stream = Stream.iterate(1, n -> n + 1).limit(10);
Object[] array = stream.toArray();
System.out.println(Arrays.toString(array));
}

collect方法

1
2
3
4
5
6
7
8
9
//4.collect方法
@Test
public void Test4(){
//<R, A> R collect(Collector<? super T, A, R> collector)
//使用给定的收集器来收集当前流中的元素。Collectors类有用于多种收集器的工厂方法。
Stream<Integer> stream = Stream.iterate(1, n -> n + 1).limit(10);
List<Integer> list = stream.collect(Collectors.toList());
System.out.println(list);
}

summarizing方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//5.summarizing方法
@Test
public void Test5(){
//static <T> Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper)
//static <T> Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)
//static <T> Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper)
//产生能够生成(Int|Long|Double)SummaryStatistics对象的收集器,通过它们可以获得将mapper应用于每个元素后所产生的结果的数量,总和,平均值,最大值和最小值。
IntSummaryStatistics summary = Stream.iterate(1, n -> n + 1).limit(10).collect(Collectors.summarizingInt(x -> x));
//在得到SummaryStatistics对象的收集器后,可以通过以下方法求取值。
long count = summary.getCount();//10
int max = summary.getMax();//10
int min = summary.getMin();//1
double average = summary.getAverage();//5.5
long sum = summary.getSum();//55
}

基本类型流

到目前为止,我们将整数收集到Stream中,这种将数据包装到包装器中再转化为流的方式是很低效的。对于其他基本类型来说,情况也一样。在流库中具有专门的类型IntStream,LongStream和DoubleStream,可以直接用来储存基本类型值,而不需要通过包装器。如果想使用short,char,byte和boolean,可以使用IntStream,对于float,可以使用DoubleStream。

在这只介绍IntStream一种,其他与IntStream类似:

创建相应的流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void Test1(){
IntStream intStream1 = IntStream.of(1, 1, 2, 3, 5);

//与前面所述的方法一样,of方法,stream方法
int[] arr = new int[]{1,2,3,4,5,6,7,8,9};
IntStream intStream2 = Arrays.stream(arr, 1, 6);

//此外还可以使用range方法
IntStream intStream3 = IntStream.range(1, 100);

//返回由当前字符串的所有Unicode码点构成的流
String str = "hello";
IntStream intStream4 = str.codePoints();
intStream4.forEach(x-> System.out.printf("%d\t",x));
}

获取流中的值

1
2
3
4
5
6
7
8
9
//获取流中的值
@Test
public void Test2(){
IntStream intStream = IntStream.of(1,2,3,4,5,6,7,8,9,10);
// OptionalInt max = intStream.max();
// OptionalInt min = intStream.min();
// OptionalDouble average = intStream.average();
int sum = intStream.sum();//55
}

并行流

一下内容参考 Java8–Stream 并行流详解

简介

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。串行流则相反,并行流的底层其实就是ForkJoin框架的一个实现。

java.util.Collection < E >新添加了两个默认方法

  • default Stream stream() : 返回串行流
  • default Stream parallelStream() : 返回并行流

将一个并行流转成顺序的流只要调用sequential()方法

stream.parallel() .filter(…) .sequential() .map(…) .parallel() .reduce();

这两个方法可以多次调用, 只有最后一个调用决定这个流是顺序的还是并发的。

想要明白并行流,那么就必须了解ForkJion框架

Fork/Join框架介绍

Fork/Join框架时java7提供的一个用于并行执行任务的框架:就是在必要的情况下,将一个大任务,进行拆分成若干个小任务(拆到不可再拆时),再将一个个小的任务运算的结果进行jion汇总。

注意:Fork/Jion框架使用的默认线程数等于你机器的处理器核心数
通过这个方法可以修改这个值,而且这个还是全局属性,不过建议一般不修改
System.setProperty(“java.util.concurrent.ForkJoinPool.common.parallelism”, “12”);

工作窃取模式

Fork/Join框架它所使用的线程模式—-工作窃取模式。每个线程都会为分配给它的任务保存一个双向链式队列,每完成一个任务,就会从队列头上取出下一个任务开始执行。基于种种原因,某个线程可能很早就完成了分配给它的任务,而其他的线程还未完成,那么这个线程就会,随机选一个线程,从队列的尾巴上“偷走一个任务”。这个过程一直继续下去,直到所有的任务都执行完毕,所有的队列都清空。

注意:使用ForkJoin时,任务的量一定要大,否则太小,由于任务拆分也会消耗时间,它执行的效率不一定比for循环高

运算速度比较

我们现在进行测试一下,从1累加到 10000000000L,看看需要多长时间:

先用我们传统的for循环进行测试:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void Test4(){
long start = System.currentTimeMillis();
long sum = 0;
for (long i = 0L; i < 10000000000L; i++) {
sum+=i;
}
System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println("耗费时间:"+(end-start));//大概5500左右
}

运行结果:

接着我们用普通串行流进行测试:

1
2
3
4
5
6
7
8
@Test
public void Test3(){
long start = System.currentTimeMillis();
long sum = LongStream.range(0, 10000000000L).sum();
System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println("耗费时间:"+(end-start));//大概4400左右
}

运行结果:

最后我们用并行流进行测试:

1
2
3
4
5
6
7
8
@Test
public void Test2(){
long start = System.currentTimeMillis();
long sum = LongStream.range(0, 10000000000L).parallel().sum();
System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println("耗费时间:"+(end-start));//大概1800左右
}

输出结果:

不多bb了,这个并行流还没怎么搞懂,以后再学习。收工!