Stream流源码分析及技巧(含大量案例)
目录
Stream流源码分析及技巧(含大量案例)
更新说明
简介(这部分摘了部分官方文档)
特性
Stream接口关系图
Stream流接口方法
Stream流之间的转换
与Stream流相关的文章
使用实例
更新说明
版本 | 编写时间 | 作者 | 说明 |
第一版 | 2023.2.14-2.15 | 蔡济民 | 初稿,JDK19,包含两个JDK16新特性 |
简介(这部分摘了部分官方文档)
支持顺序和并行聚合操作的元素序列
为了执行计算,流操作被组合到流管道中。流管道由源(可能是数组、集合、生成器函数、IO 通道等)、零个或多个中间操作(将一个流转换为另一个流,例如 filter(谓词))和一个终端操作(产生结果或副作用,例如 count() 或 forEach(Consumer))组成。流是懒惰的;仅在启动终端操作时对源数据执行计算,并且仅在需要时使用源元素。
流实现在优化结果计算方面允许很大的自由度。
与集合不同,集合更多的在于存储和取出,流更关注对元素的操作
流里面的元素:必须是非干扰的(它们不修改流源);并且在大多数情况下必须是无状态的(其结果不应依赖于在流管道执行期间可能更改的任何状态)。
一个流只能操作一次(调用中间流或终端流操作)。例如,这排除了“分叉”流,其中同一源馈送两个或多个管道,或同一流的多个遍历。流实现可能会引发IllegalStateException
流有一个 close() 方法并实现 AutoCloseable。在流关闭后对流进行操作将引发 IllegalStateException。
流管道可以按顺序或并行执行。此执行模式是流的属性。流是通过顺序执行或并行执行的初始选择创建的。(例如,Collection.stream() 创建一个顺序流,而 Collection.parallelStream() 创建一个并行流。这种执行模式的选择可以通过 sequential() 或 parallel() 方法修改,也可以使用 isParallel() 方法进行查询。
特性
stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。
Stream接口关系图
祖先接口
AutoCloseable接口,提供了一个自动关闭的方法close()
父类接口
BaseStream接口,提供流之间的操作
比如生成一个流的关键在于确定如何求值下一项元素,所以就需要迭代器iterator。
需要拆分元素,返回此流的元素的拆分器。就需要spliterator,这是一个终端操作,为什么需要拆分,就是为部分元素计算整个流管道的代价过于昂贵。
是否并行执行就用到isParallel()
返回一个顺序流就用到sequential();同样返回无序流就是unorder();
parallel()返回一个并行流
onClose()提供关闭流的一个方法,是一个中间操作,返回具有附加关闭处理程序的等效流。
有了以上内容就可以思考,如何产生一个无穷的整数流呢
Stream流接口方法
使用的比较多的我用蓝色加粗标注
Stream<T> filter(Predicate<? super T> predicate);过滤器,参数是逻辑表达式
比如.filter(s->s!=1),就是取出不为1的流元素,成一个新流。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
将一个Stream映射到另外一个Stream,参数是接受一个函数,这个函数会被应用到所有元素上,将他们映射成一个新的元素。这是一个惰性求值的方法(就是用到的时候才会调用,这是来解决数据过于大的流,比如斐波那契数列等)
再补充一下惰性求值
比如
list.stream().filter(x -> {System.out.println(x);return true;});//这一段什么都不输出
但是在后面加上.count()后就会输出了,因为遇到最终操作才会真正拉动。
IntStream mapToInt(ToIntFunction<? super T> mapper);
MapToInt,将map转为IntStream流,举例如下:
List<String> list=new ArrayList<>();
Collections.addAll(list,"11","22","33");
这里用一个集合来装一系列字符串,如果我们想对它求和,直接使用list.stream().sum会发现没有这个方法,为什么呢,因为是String流,我们需要转换为int才可以处理。所以我们使用maptoInt,编写一个匿名内部类
list.stream().mapToInt(new ToIntFunction<String>() {
@Override
public int applyAsInt(String value) {
return Integer.parseInt(value);
}
}).sum()
return那一行我们使用Integer的parseInt方法直接将字符串转为整数。我们可以用方法引用简写一下,就会很清晰list.stream().mapToInt(Integer::parseInt).sum(),到此就执行了一个字符串集合(存的是数字)的求和操作。
Maptodouble之类的类似理解就可。
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
这里要着重指明一下flatMap跟map之间的区别, flatMap方法返回的是一个stream,flatMap将流中的当前元素替换为此返回流拆解的流元素。相当于是一个扁平化操作。
这篇博文讲的例子是非常好的:
java8 stream流操作的flatMap(流的扁平化)_Mark_XC的博客-CSDN博客_stream().flatmap()
flatmaptoInt ,按照maptoInt类似理解就行。
default <R> Stream<R> mapMulti(BiConsumer<? super T, ? super Consumer<R>> mapper)
mapMulti,这是Java16引入的新方法,这点我看了几个例子,勉强理解了,他的用法是用少量(可能为零)的元素替换每个流元素时
Stream API 中的 mapMulti 指南
这篇文章写得不错
Java 16 中新增的 Stream 接口的一些思考 - 知乎
这篇文章我看了勉强理解了
Java 16 新方法 Stream.mapMult | w3c笔记
这篇文章举的大小写的例子,不需要flatMap后再嵌套Stream.of了
我目前理解地就是大小写替换会更简洁的写法,不用filter再多定义一个流,代码更简洁,并且stream里面就可以分组
我给出了以下两种例子,跟一种区别
Stream.of("Twix", "Snickers", "Mars")
.mapMulti((s, c) -> {
c.accept(s.toUpperCase());
c.accept(s.toLowerCase());
c.accept(s.substring(0,3));
})
.forEach(System.out::println);
List<String> list1 = Stream.of("Twix", "Snickers", "Mars")
.flatMap(s -> Stream.of(s.toLowerCase()))
.toList();
List<String> list = Stream.of("Twix", "Snickers", "Mars")
.<String>mapMulti((s, c) -> {
c.accept(s.toLowerCase());
}).toList();
System.out.println(list1);
System.out.println(list);
Stream<T> distinct();
Distinct就是去重,这里有两点需要注意,Stream源码note中写的很好。
对于有序流,不同元素的选择是稳定的(对于重复的元素,将保留在遭遇顺序中首先出现的元素。对于无序流,不做稳定性保证。
在并行管道中保持 distinct() 的稳定性相对昂贵(要求操作充当完全屏障,具有大量的缓冲开销),并且通常不需要稳定性。使用无序流源(例如 generate(Supplier))或使用 unordered() 删除排序约束可能会导致并行管道中 distinct() 的执行效率显着提高,如果您的情况允许的话。如果需要与遭遇顺序保持一致,并且您在并行管道中使用 distinct() 时遇到性能或内存利用率不佳的情况,则切换到使用 sequential() 的顺序执行可能会提高性能。
Stream<T> sorted();
排序,按自然顺序排序,如果不可排序,会抛出ClassCastException异常
Stream<T> sorted(Comparator<? super T> comparator);
排序,按比较器的方式进行排序
Stream<T> peek(Consumer<? super T> action);
Peek是来返回流元素的,是一个中间方法,主要用于调试。
只有最终操作,类似于foreach的时候,元素才会被真正拉动,跟我们前面说的惰性求值一样
这里直接把源码这一段写的不错的贴过来
Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " +e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
注意:这里是用于调试的,只有加上最终方法,peek里的输出才会输出
Stream<T> limit(long maxSize);
Limit就是取前n个元素,但是这里官方文档给了一点要注意的,我直接贴过来
虽然 limit() 在顺序流管道上通常是一个便宜的操作,但在有序并行管道上它可能非常昂贵,特别是对于 maxSize 的大值,因为 limit(n) 被约束为不仅返回任何 n 个元素,而且返回遭遇顺序中的前 n 个元素。如果您的情况的语义允许,使用无序流源(例如 generate(Supplier))或使用 unordered() 删除排序约束可能会导致并行管道中 limit() 的显著加速。如果需要与遭遇顺序保持一致,并且您在并行管道中使用 limit() 时遇到性能或内存利用率不佳的问题,则切换到使用 sequential() 的顺序执行可能会提高性能。
Stream<T> skip(long n);
同limit,取从n开始的元素,一样的问题
这一段我问了专家为什么stream流为什么会设计这么多提高性能的办法,比如惰性求值这种,这些方法不也就是求和,取元素之类的,但是String里面也有很多类似的方法,但是为什么不设计这些提高性能的方法呢。
老师说的是,String出现的比较早,stream流主要是方便我们代码编写,融合了很多数据类型,可能时间复杂度就更高。
我感觉可能还是不太理解,不过我找了这一篇关于stream流运行效率的问题
这个是实验结果
【Java】stream效率问题_嗨森bao的博客-CSDN博客_stream效率
这个是实验结果的总结
Java8 Stream 数据流,大数据量下的性能效率怎么样?_Java_xcbeyond_InfoQ写作社区
default Stream<T> takeWhile(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
一样在并行管道上会有时间效率问题
跟filter的区别,takeWhile
将在第一次出现不满足条件的项目时中止流。
而filter
将从流中删除所有不满足条件的项目。
举例说明
Stream.of(1,2,3,4,5,6,7,8,9,10,9,8,7,6,5,4,3,2,1)
.filter(i -> i < 4 )
.forEach(System.out::print);
打印123321
Stream.of(1,2,3,4,5,6,7,8,9,10,9,8,7,6,5,4,3,2,1)
.takeWhile(i -> i < 4 )
.forEach(System.out::print);
打印123
Tropwhile同理takewhile
排除满足条件元素,直到不满足条件为止
void forEach(Consumer<? super T> action);
对每一个元素进行操作,是最终操作
官方文档写的不错
此操作的行为显然是不确定的。对于并行流管道,此操作不保证遵守流的遭遇顺序,因为这样做会牺牲并行性的好处。对于任何给定元素,可以在库选择的任何时间和任何线程中执行操作。如果操作访问共享状态,则它负责提供所需的同步。
void forEachOrdered(Consumer<? super T> action);
同理forEach不过是有序时候的操作
Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);
将流中的元素转成数组存放,是最终操作,这里大概没有什么需要多说的
T reduce(T identity, BinaryOperator<T> accumulator);
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
Reduce是一个最终操作
这里直接贴官方文档的解释吧,讲的很清楚
reduction 操作(也称为fold),通过反复的对一个输入序列的元素进行某种组合操作(如对数的集合求和、求最大值,或者将所有元素放入一个列表),最终将其组合为一个单一的概要信息。stream类包含多种形式的通用reduction操作,如reduce和collect,以及其他多种专用reduction形式:sum,max或者count。
总和、最小值、最大值、平均值和字符串连接都是简化的特殊情况。对数字流求和可以表示为:整数和 = integers.reduce(0, (a, b) -> a+b);或:整数和 = 整数.reduce(0, 整数::总和);虽然与简单地改变循环中的运行总计相比,这似乎是一种更迂回的执行聚合的方式,但缩减操作可以更优雅地并行化,无需额外的同步,并且大大降低了数据争用的风险。
一般用法是这么用:
Integer sum = integers.reduce(0, (a, b) -> a+b);
Integer sum = integers.reduce(0, Integer::sum);
另外reduce可以并发计算,很强,并发计算的代码我忘记是哪篇文章了
这篇深一点,讲reduce功能的
一文讲透 Stream.reduce()_codingstyle的博客-CSDN博客_stream.reduce
这篇讲reduce使用例子的,有很多
https://www.cnblogs.com/gaohanghang/p/12390233.html
<R, A> R collect(Collector<? super T, A, R> collector);
主要是接收一个Collector实例,将流中元素收集成另外一个数据结构。
https://www.cnblogs.com/owenma/p/12207330.html
这篇文章写得太好了,直接看他总结的collect部分,还有实例,我写的练习实例在后面
collect方法是强制求值方法中,最复杂也最强大的接口,其作用是将流中的元素收集(collect)起来,并转化成特定的数据结构。
从函数式编程的角度来看,collect方法是一个高阶函数,其接受三个函数作为参数(supplier,accumulator,finisher),最终生成一个更加强大的函数。在java中,三个函数参数以Collector实现对象的形式呈现。
supplier 方法:用于提供收集collect的初始值。
accumulator 方法:用于指定收集过程中,初始值和流中个体元素聚合的逻辑。
finnisher 方法:用于指定在收集完成之后的收尾转化操作(例如:StringBuilder.toString() ---> String)。
注意:Collectors工具类,很好的满足了平时常见的需求(Collector.toList()、Collctor.groupingBy())等
@SuppressWarnings("unchecked")
default List<T> toList()
这里注解@SuppressWarnings("unchecked")的作用是 告诉编译器忽略 unchecked 警告信息,如使用List,ArrayList等未进行参数化产生的警告信息。
这个功能主要是将Stream流转List,是最终操作
Java16版本才可以使用。如下.toList()
Java8的话是使用.collect(Collectors.toList())
主要区别
toList()不能增删改,比如我写的下面这段代码
//toList后,不能增删改
// List<String> list = Stream.of("aaa", "bbb", "ccc").toList();
// System.out.println(list.add("ddd"));
//用Collectors.toList()之后就可以增删改了
List<String> collect = Stream.of("aaa", "bbb", "ccc").collect(Collectors.toList());
System.out.println(collect.add("ddd"));
一看就懂了
Collectors.toUnmodifiableList()是Collectors不能增删改的方法
list.stream().collect(Collectors.toUnmodifiableList());
这篇文章是专门讲toList跟Collectors.toList的性能的
Stream.toList()和Collectors.toList()的性能比较 - 知乎
Optional<T> min(Comparator<? super T> comparator);
就是求最小值的方法,注意:如果最小值为空时会抛出空指针异常
Max同理
long count();
就是流中元素的计数,这个很好理解,用官方文档这一段说明不错,等同于
mapToLong(e -> 1L).sum();
boolean anyMatch(Predicate<? super T> predicate);
另外两个放在一起说
anyMatch:判断的条件里,任意一个元素成功,返回true
allMatch:判断条件里的元素,所有的都是,返回true
noneMatch:与allMatch相反,判断条件里的元素,所有的都不是,返回true
Optional<T> findFirst();
返回流的第一个元素,注意:如果所选的元素为空,抛出空指针异常
Optional<T> findAny();
返回流中的任一一个元素
public static<T> Builder<T> builder() {
return new Streams.StreamBuilderImpl<>();
}
返回一个Stream构造器,可以用build方法创建Stream
一般我们用Arrays.of,List.of或者Stream.of创建
这里可以使用Stream.Builder<String> builder = Stream.builder();
Builder.add(“abc”);//这也是创建流的一种方式
只不过前两种一般更简洁一点
public static<T> Stream<T> empty() {
return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);
}
创建空的Stream流,暂时忽略吧,我也不知道有啥大用
public static<T> Stream<T> of(T t)
返回单个元素顺序流,这个我们经常用到
Stream.of(“aaa”,”bbb”)这样的
Stream.ofNullable一起放在这里
返回包含单个元素的顺序流(如果非 null),否则返回空流。
public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
主要是用来 创建无数个有关系的元素的流
比如Stream.iterate("1", b -> b+"0").limit(3).forEach(System.out::println);
就是1,10,100
再举个例子,斐波那契数列的例子
Stream.iterate(new int[]{0, 1}, n -> new int[]{n[1], n[0] + n[1]}) .limit(20) .map(n -> n[0]) .forEach(x -> System.out.println(x));
这里还有一篇博客也是写的斐波那契数列
Stream.iterate() 生成 斐波那契数列(Fibonacci)_起名-困难户的博客-CSDN博客_stream.iterate
public static<T> Stream<T> generate(Supplier<? extends T> s)
通过generate()生成无限流数据,跟iterate有点像,但这个更加暴力,比如生成随机整数流
Stream.generate(() -> new Random().nextInt(10));
随机布尔流
Stream.generate(() -> new Random().nextBoolean())
.forEach(e -> System.out.println(e));
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
将两个Stream流按顺序进行拼接成一个新的Stream流
Stream流之间的转换
Int类型的数组转为流的时候要进行装箱.boxed()
装箱之后可以通过collect(Collectors.toList())来转成List<Integer>
一样是.boxed()装箱后的数据通过.toArray(Integer:new)转为Integer
未装箱的数据IntStream流,或者List<Integer>流通过.mapToInt (Integer::valueOf).toArray();转为int[]
List< Integer > 通过list.toArray(new Integer[list.size()]);转成 Integer[ ]
转换 Int[] 至 String
String s = Arrays.stream(arr).boxed().map(i->i.toString()).collect(Collectors.joining());
与Stream流相关的文章
这篇是从0开始自己搭建一个Stream流,不是使用
https://www.cnblogs.com/xiaoxiongcanguan/p/10511233.html
这篇文章讲的也挺不错
【java基础】吐血总结Stream流操作_九离⠂的博客-CSDN博客
使用实例
以下案例均为本人写的代码,转载请标明出处
1.计算偶数个数,难度1星
给一个整型数组,统计该数组中偶数元素的个数, 返回结果
输入参数 | 输出结果 |
[2,1,2,3,4] | 3 |
[2,2,0] | 3 |
[1,3,5] | 0 |
public int countEvens(int[] nums) {
return (int) Arrays.stream(nums).filter(s -> s % 2 == 0).count();
}
2.幸运的1和3,难度1星
给定一个整型数组,如果数组不包含1和3,则返回true。
输入参数 | 输出结果 |
[0,2,4] | true |
[1,2,3] | false |
[1,2,4] | false |
return Arrays.stream(arr).noneMatch(s->s==1||s==3);
3.相邻索引都是2,3星难度
给定一个整型数组,如果数组中出现两个相邻的索引中的元素都是2,则返回true
输入参数 | 输出结果 |
[1,2,2] | true |
[1,2,1,2] | false |
[2,1,2] | false |
此题解析我写到例9去了
String collect = Arrays.stream(arr).boxed().map(i -> i.toString()).collect(Collectors.joining());
return collect.matches(".*22.*");
4.圆周率N,难度3星
返回一个包含Pi(参考 Math.PI - π )的前n位数字的整数数组长度,n为方法接收的参数。
例如:n为3,则返回 [3,1,4]。
输入参数 | 输出结果 |
1 | [3] |
2 | [3,1] |
3 | [3,1,4] |
/*
这一段是介绍下面的代码
Arrays.stream(strings)这一步处理完是一个Stream,里面放了两个String 3 14159...
我们需要把这两个String合并成一个String再转int再转数组输出
.map(s -> s.split(""));
将Stream<String>转成Stream<String[]>这样我们能对里面每个元素进行操作,但还是两个
.flatMap(Arrays::stream)
通过flatMap将元素扁平化,拉一起合并成一个Stream<String>
.mapToInt(Integer::valueOf)
转成IntStream流
.limit(n)
取前n个元素
.toArray();
转成int数组
这个网址有图示化stream流
https://blog.csdn.net/Mark_Chao/article/details/80810030
*/
public int[] makePiN(int n) {
double pi = Math.PI;
String pistr = String.valueOf(pi);
//注意这里正则表达要写成\\.不然就是任意字符
String[] strings = pistr.split("\\.");
int[] arr =Arrays.stream(strings)
.map(s -> s.split(""))
.flatMap(Arrays::stream)
.mapToInt(Integer::valueOf)
.limit(n)
.toArray();
return arr;
}
5.数字是数组的长度,难度2星
给定义个整型数字(已知>=0),返回一个长度是该数字的整型数组,该数组中的元素按自然顺序递增, 例如数字是7,那么返回长度为7的整型数组:
数组的元素为[0,1,2,3,4,5,6]
输入参数 | 输出结果 |
4 | [0,1,2,3] |
1 | [0] |
10 | [0,1,2,3,4,5,6,7,8,9] |
//这个题主要是用到iterate迭代产生从0开始的自然数序列
public int[] fizzArray(int a) {
return Stream.iterate(0, s->s+1).limit(a).mapToInt(Integer::intValue).toArray();
}
6.数字长度的字符串,难度2星
给一个任意的数字,返回一个长度是该数字的 字符串 数组,该数组中的元素按自然顺序递增,
例如数字是4,那么返回长度为4的字符串数组,数组的元素为 ["0","1","2","3"]
输入参数 | 输出结果 |
4 | ["0","1","2","3"] |
10 | ["0","1","2","3","4","5","6","7","8","9"] |
2 | ["0","1"] |
public String[] fizzArray2(int a) {
/*
之前例题写过的就不写了
.map(String::valueOf),对数字处理,转字符串
0 1 2 3 4 5 ->Stream流上一共a个String类型,Stream<String>
.toArray(String[]::new),把单个单个字符串转成字符串字符串数组,长度为a
*/
return Stream.iterate(0, s -> s + 1).limit(a)
.map(String::valueOf).toArray(String[]::new);
}
7.从开始到结束,难度2星
给定两个int数,一个是开始值一个是结束值, 返回一个数组。
这个数组是由开始值每一次递增1到结束的数据,数组中包含开始值,但不包含结束值。
注意:如果开始和结束相等,则返回一个空数组
输入参数 | 输出结果 |
5, 10 | [5,6,7,8,9] |
11, 18 | [11,12,13,14,15,16,17] |
1, 3 | [1,2] |
return Stream.iterate(0, s -> s + 1).limit(end).skip(start).mapToInt(Integer::intValue).toArray();
8.去掉最大和最小后的平均值,难度1星
给定一个整型数组,去掉数组中最大和最小的值, 返回其他元素的平均值
输入参数 | 输出结果 |
[1,2,3,4,100] | 3 |
[1,1,5,5,10,8,7] | 5 |
[-10,-4,-2,-4,-2,0] | -3 |
public int centeredAverage(int[] nums) {
int sum=Arrays.stream(nums).sum();
int min= Arrays.stream(nums).min().getAsInt();
int max=Arrays.stream(nums).max().getAsInt();
return (sum-min -max )/(nums.length-2);
}
9.元素3间隔的出现3次,难度4星
给定一个整型数组,如果元素3在数组中 恰好 出现3次,并且中间都有一个其他元素则返回true, 例如[3, 1, 3, 2, 3]返回true
输入参数 | 输出结果 |
[3,1,3,1,3] | true |
[3,1,3,3] | false |
[3,4,3,3,4] | false |
//这个题是有点质量的,需要思考一下int[]转string,这种转换并不常见,如果用 Arrays.toString会多出[, ]这些东西,流转换是用到Collectors.joining(),我们使用很少
public boolean haveThree(int[] arr) {
if (Arrays.stream(arr).filter(s->s==3).count()!=3) {
return false;
}
else {
String s = Arrays.stream(arr).boxed().map(i->i.toString()).collect(Collectors.joining());
System.out.println(s);
return s.matches(".*3.3.3.*");
}
}
此题类似例9,数组还有77,我不列出解答了
10.给定一个整型数组,如果数组包含两个彼此相邻的7,或者有两个7由一个元素分隔, 如
[7, 7]返回true,
[7,1,7]也要返回true;
否则返回false
还有一个类似的,数组含有12,思想都是一模一样的
给定一个整型数组,如果数组中有1元素, 并且在1元素后面的元素中还有2元素,
则返回true, 否则返回false.
11.最大与最小元素的差值,1星
给定一个整型数组(长度>=1),返回数组中最大值和最小值之间的差值。
输入参数 | 输出结果 |
[10,3,5,6] | 7 |
[7,2,10,9] | 8 |
[2,10,7,2] | 8 |
public int bigDiff(int[] arr) {
return Arrays.stream(arr).max().getAsInt()- Arrays.stream(arr).min().getAsInt();
}
12.左移一位数,2星
给定一个整型的数组,将数组的元素左移一位后返回一个新的数组。
例如:[6,2,5,3] 返回 [2,5,3,6]
输入参数 | 输出结果 |
[6,2,5,3] | [2,5,3,6] |
[1,2] | [2,1] |
[1] | [1] |
不难,懒得写注释了
public int[] shiftleft(int[] arr) {
if(arr.length==0) return new int[0];
int[] ints = Stream.concat(Arrays.stream(arr).boxed(), Stream.of(arr[0]))
.skip(1)
.mapToInt(Integer::intValue).toArray();
return ints;
}
13.之前的数组,3星
这个题我给三星的原因是很难想到takewhile()毕竟这个用的不多
给定一个非空的int类型的数组, 返回一个新数组。
新数组中存放的是原数组中从第一个元素开始直到第一个4为止(不包含)的数组。
如果第一个是4,则返回一个空数组,已知数组中一定存在4。
输入参数 | 输出结果 |
[1,2,4,1] | [1,2] |
[3,1,4] | [3,1] |
[1,4,4] | [1] |
public int[] pre4(int[] nums) {
if(nums[0]==4){
return new int[0];
}
else
return Arrays.stream(nums).takeWhile(i -> i != 4).toArray();
}
比一般的暴力做法简单多了。
15.1和4次数比较,2星
给定一个整型数组,如果1的出现次数大于4出现的次数,则返回true
输入参数 | 输出结果 |
[1,4,1] | true |
[1,4,1,4] | false |
[1,1] | true |
public boolean more14(int[] arr) {
int count1= (int) Arrays.stream(arr).filter(s->s==1).count();
int count4= (int) Arrays.stream(arr).filter(s->s==4).count();
if (count1>count4) return true;
else return false;
}
16.所有2的总和是8
给定一个整型数组,如果数组中所有2的总和为8,则返回true。
输入参数 | 输出结果 |
[2,3,2,2,4,2] | true |
[2,3,2,2,4,2,2] | false |
[1,2,3,4] | false |
public boolean sum28(int[] arr) {
if (Arrays.stream(arr).filter(s->s==2).sum()==8) {
return true;
}
return false;
}
17.不存在1和4元素的数组,2星
给定一个整型数组,如果它同时包含1和4, 返回false,否则返回true;
输入参数 | 输出结果 |
[1,2,3] | true |
[1,2,3,4] | false |
[2,3,4] | true |
public boolean no14(int[] arr) {
if (Arrays.stream(arr).anyMatch(a->a==1)&& Arrays.stream(arr).anyMatch(a->a==4)) {
return false;
}
return true;
}
18.2和4任意一个条件满足,4星
给定一个整型数组,如果数组中含有两个相邻的数字2,或是两个相邻的数字4,就返回true,否则返回false,
但是!如果数组既含有两个相邻的数字2又含有两个相邻的数字4,还返回false。
例如:([1, 2, 3, 2, 2, 4, 4]) -> false
这个也是前面的知识点应用,我给4星的原因是因为前面那个整形数组转字符串是4星,并且你不用Stream流+正则表达做,代码起码多10几行得再写个函数判断逻辑
public boolean either24(int[] arr) {
String collect = Arrays.stream(arr).boxed().map(i -> i.toString()).collect(Collectors.joining());
boolean istwo=collect.matches(".*22.*");
boolean isfour=collect.matches(".*44.*");
if(istwo&&isfour) return false;
else if(istwo||isfour) return true;
else return false;
}
19.把0移到前面,4.5星
给定一个int类型的数组, 将数组中的0都移到前面,其他值向后顺延
输入参数 | 输出结果 |
[1,0,0,1] | [0,0,1,1] |
[0,1,1,0,1] | [0,0,1,1,1] |
[1,0] | [0,1] |
此题给4.5星的原因是要想到0放前面,非0放后面,实际就是两个过滤的拼接,我感觉是不错,有技巧的。所以单独给它加了0.5星
public int[] zeroFront(int[] nums) {
int[] ints = Stream.concat(Arrays.stream(nums).boxed().filter(s -> s == 0),
Arrays.stream(nums).boxed().filter(s -> s != 0))
.mapToInt(Integer::intValue).toArray();
return ints;
}
有个类似的题,把0移到后面
给定一个int类型的数组,先将数组中所有的10变成0, 然后将数组中的0都移到后面,其他的值向前顺延
不写样例跟代码了,一样的思路
20.4之后的数组,4星
给定一个非空的int类型的数组, 返回一个新数组。
新数组中存放的是原数组中从最后一个4开始到末尾的数组。
如果原数组中最后一个元素是4,则返回空数组。
已知:数组中一定存在4。
输入参数 | 输出结果 |
[2,4,1,2] | [1,2] |
[4,1,4,2] | [2] |
[4,4,1,2,3] | [1,2,3] |
此题算是综合运用,但并不难
public int[] post4(int[] nums) {
int index=-1;
for (int i = nums.length-1; i >=0 ; i--) {
if(nums[i]==4){
index=i;
break;
}
}
//这里只需要调整下面的index+1就行
int[] ints = Arrays.stream(nums).boxed().skip(index+1).mapToInt(Integer::intValue).toArray();
return ints;
}
偶数在前,奇数在后,4星
给定一个int类型数组(长度任意), 将所有的偶数放到奇数的前面. 然后返回一个修改后的数组
注意:这个数组只有两个值重复出现。
输入参数 | 输出结果 |
[1,0,1,0,0,1,1] | [0,0,0,1,1,1,1] |
[3,3,2] | [2,3,3] |
[2,2,2] | [2,2,2] |
前面有个题跟此题思路也是一样的,所以我打分给他降了0.5星
return Stream.concat(Arrays.stream(nums).boxed().filter(s -> s % 2 == 0),
Arrays.stream(nums).boxed().filter(s -> s % 2 != 0))
.mapToInt(Integer::intValue).toArray();