一、基本概念

1、Stream是一个高级的迭代器,不是数据结构,不是一个集合,不会存放数据。关注的是数据高效的处理,数据在一个流水线中执行。

2、外部迭代和内部迭代



public class StreamDemo1 {
public static void main(String[] args) {
int[] nums = {1, 2, 4};
// 外部迭代
int sum = 0;
for(int i : nums){
sum += i;
}
System.out.println("结果为:" + sum);

//使用stream的内部迭代
int sum2 = IntStream.of(nums).sum();
System.out.println("结果为:" + sum2);

}
}


  

3、中间操作/终止操作和惰性求值



//使用stream的内部迭代
//map就是中间操作,每个数字乘以2 (返回stream的操作)
//sum 就是终止操作
int sum2 = IntStream.of(nums).map(i -> i * 2).sum();
System.out.println("结果为:" + sum2); //结果为:14


  

4、惰性求值

java 8 Stream流编程_数据 

 

 返回类型是流,就是中间操作; 返回类型是结果,就是终止操作。

 

二、流创建

java 8 Stream流编程_数据_02

 

 如下面的代码



public class StreamDemo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//1、从集合创建
list.stream();
list.parallelStream(); //并行流

// 2、从数组创建
Arrays.stream(new int[]{2,5,8});

// 3、创建数字流
IntStream.of(1,5,8);
IntStream.rangeClosed(1,10); // 1到10的数字流

//使用random创建一个无限流, 并指定limit
new Random().ints().limit(10);

// 4. 自己产生流
Random random = new Random();
Stream.generate(() -> random.nextInt()).limit(20);


}


}


  

三、流的中间操作

包括无状态操作和有状态操作。

无状态表示操作跟前后没有依赖关系。

有状态表示结果要依赖于其它元素。如排序操作,依赖所有的元素,才能计算结果。

java 8 Stream流编程_线程池_03

1、把每个单词的长度打印出来



public static void main(String[] args) {
String str = "my name is larry";

//把每个单词的长度打印出来
Stream.of(str.split(" ")).filter(s -> s.length() > 2).map(s -> s.length()).forEach(System.out::println);

}


filter 过滤长度大于2的字符

map: 每个单词的长度

 

2、flatmap使用



public static void main(String[] args) {
String str = "my name is larry";

//把每个单词的长度打印出来
Stream.of(str.split(" ")).filter(s -> s.length() > 2).map(s -> s.length()).forEach(System.out::println);

//flatMap A -> B属性(是个集合),最终得到A元素里面所有B属性集合。
// intStream/longStream 并不是Stream的子类,所以要进行装箱boxed
Stream.of(str.split(" ")).flatMap( s -> s.chars().boxed()).forEach(
i -> System.out.println((char)i.intValue()));;
}


  

输出结果



m
y
n
a
m
e
i
s
l
a
r
r
y


  

这里A就是单词,B就是单词的字符。

 

3、peek使用



public static void main(String[] args) {
String str = "my name is larry";

//peek 用于debug,是个中间操作,和forEach是终止操作
System.out.println("-------------peek-----------------");
Stream.of(str.split(" ")).peek(System.out::println).forEach(System.out::println);
}


  

输出结果为:



-------------peek-----------------
my
my
name
name
is
is
larry
larry


  

4、limit操作



// limit使用,主要用于无限流
new Random().ints().filter(i -> i > 100 && i < 1000).limit(10)
.forEach(System.out::println);


输出结果:



273
628
601
769
528
114
676
871
531
862


  

四、Stream流编程--终止操作

分为非断路操作和短路操作

java 8 Stream流编程_格式转换_04

 常用的终止操作



public class StreamDemo4 {
public static void main(String[] args) {
String str = "my name is larry";

// 使用并行流
str.chars().parallel().forEach(i -> System.out.print((char)i)); // 输出结果是乱的: mnrmyay laries
System.out.println();
// 使用forEachOrdered保证顺序
str.chars().parallel().forEachOrdered(i -> System.out.print((char)i)); // // 输出结果是正确的: my name is larry

//收集到list
System.out.println();
List<String> list = Stream.of(str.split(" ")).collect(Collectors.toList());
System.out.println(list); //输出结果: [my, name, is, larry]

// 使用reduce拼接字符串
Optional<String> letters = Stream.of(str.split(" ")).reduce((s1, s2) -> s1 + "|" + s2);
System.out.println(letters.orElse("")); // 输出结果: my|name|is|larry


// 带有初始化值的reduce
String reduce = Stream.of(str.split(" ")).reduce("",(s1, s2) -> s1 + "|" + s2);
System.out.println(reduce);

// 计算所有单词总长度
Integer length = Stream.of(str.split(" ")).map(s -> s.length()).reduce(0, (s1, s2) -> s1 + s2);
System.out.println(length); // 输出结果: 13

// max的适应
Optional<String> max = Stream.of(str.split(" ")).max((s1, s2) -> s1.length() - s2.length());
System.out.println(max.get()); //输出结果: larry

// 使用findFirst 短路操作
OptionalInt findFirst = new Random().ints().findFirst();
System.out.println(findFirst.getAsInt()); // 输出结果: 1554210202

}

}


  

 五、并行流

1、串行流



public class StreamDemo5 {
public static void main(String[] args) {
//串行打印
IntStream.range(1,100).peek(StreamDemo5::debug).count();

}

public static void debug(int i){
try {
Date date = new Date(); // 获取当前系统时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 设置日期格式
String strTime = simpleDateFormat.format(date); // 格式转换
System.out.println(strTime + ":debug " + i);
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}

}

}


输出结果:可以发现,打印是串行的,没隔3秒钟打印一条



2021-01-18 10:03:43:debug 1
2021-01-18 10:03:46:debug 2
2021-01-18 10:03:49:debug 3
2021-01-18 10:03:52:debug 4


 

2、并行流  

将上面代码增加parallel()



//调用parallel 产生一个并行流
IntStream.range(1,100).parallel().peek(StreamDemo5::debug).count();


 输出结果:



2021-01-18 10:10:29:debug 81
2021-01-18 10:10:29:debug 4
2021-01-18 10:10:29:debug 7
2021-01-18 10:10:29:debug 16
2021-01-18 10:10:29:debug 90
2021-01-18 10:10:29:debug 43
2021-01-18 10:10:29:debug 31
2021-01-18 10:10:29:debug 65
2021-01-18 10:10:32:debug 91
2021-01-18 10:10:32:debug 32
2021-01-18 10:10:32:debug 66
2021-01-18 10:10:32:debug 82
2021-01-18 10:10:32:debug 5
2021-01-18 10:10:32:debug 8
2021-01-18 10:10:32:debug 17
2021-01-18 10:10:32:debug 44


可以发现,每次打印了8条。这个是CPU个数一致的。

 

3、先并行流,再串行流



public class StreamDemo5 {
public static void main(String[] args) {

// 先并行,再串行
IntStream.range(1,100)
//调用parallel 产生一个并行流
.parallel().peek(StreamDemo5::debug)
//调用sequential 产生一个串行流
.sequential().peek(StreamDemo5::debug2)
.count();
}

public static void debug(int i){
try {
Date date = new Date(); // 获取当前系统时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 设置日期格式
String strTime = simpleDateFormat.format(date); // 格式转换
System.err.println(strTime + ":debug " + i);
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}

}

public static void debug2(int i){
try {
Date date = new Date(); // 获取当前系统时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 设置日期格式
String strTime = simpleDateFormat.format(date); // 格式转换
System.out.println(strTime + ":debug2 " + i);
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}

}

}


 输出结果:



2021-01-18 10:16:43:debug 1
2021-01-18 10:16:46:debug2 1
2021-01-18 10:16:49:debug 2
2021-01-18 10:16:52:debug2 2
2021-01-18 10:16:55:debug 3
2021-01-18 10:16:58:debug2 3


 结论:  多次调用 parallel / sequential, 以最后一次调用为准

 

4、并行流使用的线程池



public class StreamDemo5 {
public static void main(String[] args) {

//并行流使用的线程池: ForkJoinPool.commonPool(JDK自带的线程池)
// 默认的线程数是 当前机器的cpu个数
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","4");
IntStream.range(1,100).parallel().peek(StreamDemo5::debug).count();
}

public static void debug(int i){
try {
Date date = new Date(); // 获取当前系统时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 设置日期格式
String strTime = simpleDateFormat.format(date); // 格式转换
System.err.println("[" + Thread.currentThread().getName() + "] " +strTime + ":debug " + i);
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}

}

}


输出结果:



[ForkJoinPool.commonPool-worker-2] 2021-01-18 10:30:38:debug 87
[ForkJoinPool.commonPool-worker-0] 2021-01-18 10:30:38:debug 81
[main] 2021-01-18 10:30:38:debug 62
[ForkJoinPool.commonPool-worker-1] 2021-01-18 10:30:38:debug 31
[ForkJoinPool.commonPool-worker-3] 2021-01-18 10:30:38:debug 13
[ForkJoinPool.commonPool-worker-3] 2021-01-18 10:30:41:debug 14
[ForkJoinPool.commonPool-worker-1] 2021-01-18 10:30:41:debug 32
[ForkJoinPool.commonPool-worker-2] 2021-01-18 10:30:41:debug 88
[main] 2021-01-18 10:30:41:debug 63
[ForkJoinPool.commonPool-worker-0] 2021-01-18 10:30:41:debug 82


每次输出4条

 

5、创建自己的线程池



public class StreamDemo5 {
public static void main(String[] args) {

//使用自己的线程池,不使用默认线程池,防止任务被阻塞
//线程名称为: ForkJoinPool-1
ForkJoinPool pool = new ForkJoinPool(5);
pool.submit(() -> IntStream.range(1,100).parallel()
.peek(StreamDemo5::debug).count());
pool.shutdown();

synchronized (pool){
try {
pool.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}

public static void debug(int i){
try {
Date date = new Date(); // 获取当前系统时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 设置日期格式
String strTime = simpleDateFormat.format(date); // 格式转换
System.err.println("[" + Thread.currentThread().getName() + "] " +strTime + ":debug " + i);
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}

}

}


输出结果:



[ForkJoinPool-1-worker-3] 2021-01-18 10:38:13:debug 31
[ForkJoinPool-1-worker-5] 2021-01-18 10:38:13:debug 56
[ForkJoinPool-1-worker-1] 2021-01-18 10:38:13:debug 65
[ForkJoinPool-1-worker-2] 2021-01-18 10:38:13:debug 90
[ForkJoinPool-1-worker-4] 2021-01-18 10:38:13:debug 81
[ForkJoinPool-1-worker-4] 2021-01-18 10:38:16:debug 82
[ForkJoinPool-1-worker-3] 2021-01-18 10:38:16:debug 32
[ForkJoinPool-1-worker-2] 2021-01-18 10:38:16:debug 91
[ForkJoinPool-1-worker-1] 2021-01-18 10:38:16:debug 66
[ForkJoinPool-1-worker-5] 2021-01-18 10:38:16:debug 57


使用线程名称为: ForkJoinPool-1,不是默认的线程池了。

 

六、收集器

收集器就是把流处理后的数据收集起来,可以收集成集合类,如List,Set。或者把处理后的数据进行再处理。



enum Gender{
MALE, FEMALE
}

enum Grade{
ONE,
TWO,
THREE,
FOUR
}

class Student {
private String name;
private int age;
private Gender gender;
private Grade grade;

public Student(String name, int age, Gender gender, Grade grade) {
this.name = name;
this.age = age;
this.gender = gender;
this.grade = grade;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public Gender getGender() {
return gender;
}

public void setGender(Gender gender) {
this.gender = gender;
}

public Grade getGrade() {
return grade;
}

public void setGrade(Grade grade) {
this.grade = grade;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", grade=" + grade +
'}';
}
}

public class CollectDemo {

public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("张三", 10, Gender.FEMALE, Grade.FOUR),
new Student("李四", 8, Gender.MALE, Grade.ONE),
new Student("王五", 18, Gender.MALE, Grade.THREE),
new Student("赵六", 20, Gender.MALE, Grade.TWO)
);

//1、得到所有学生的年龄列表
// s -> s.getAge() --> Student::getAge, 不会多生成一个类似lamda$0 这样的函数
List<Integer> ages = students.stream().map(Student::getAge).collect(Collectors.toList());
System.out.println("所有学生的年龄:" + ages); // 输出结果: 所有学生的年龄:[10, 8, 18, 20]


// //输出结果: 年龄汇总信息:IntSummaryStatistics{count=4, sum=56, min=8, average=14.000000, max=20}
//2、 count=4 总共4个学生, 所有学生的年龄和为56, 最小值为min=8, 最大值为max=20, 平均值为 average=14.000000
IntSummaryStatistics agesSummaryStatistics = students.stream().collect(Collectors.summarizingInt(Student::getAge));
System.out.println("年龄汇总信息:" + agesSummaryStatistics);

//3、 分块
Map<Boolean, List<Student>> genders = students.stream().collect(
Collectors.partitioningBy(s -> s.getGender() == Gender.MALE));
//System.out.println("男女学生列表:" + genders);
//System.out.println();
// 引入依赖 commons-collections4-4.1.jar
MapUtils.verbosePrint(System.out, "男女学生列表", genders);

// 4. 分组 按班级分组
Map<Grade, List<Student>> grades = students.stream().collect(Collectors.groupingBy(Student::getGrade));
MapUtils.verbosePrint(System.out, "学生班级列表", grades);

// 5、得到所有班级学生的个数
Map<Grade,Long> gradesCount = students.stream().collect(
Collectors.groupingBy(Student::getGrade, Collectors.counting()));
MapUtils.verbosePrint(System.out, "每个班级学生个数列表", gradesCount);

}



}


  

输出结果如下:



所有学生的年龄:[10, 8, 18, 20]
年龄汇总信息:IntSummaryStatistics{count=4, sum=56, min=8, average=14.000000, max=20}
男女学生列表 =
{
false = [Student{name='张三', age=10, gender=FEMALE, grade=FOUR}]
true = [Student{name='李四', age=8, gender=MALE, grade=ONE}, Student{name='王五', age=18, gender=MALE, grade=THREE}, Student{name='赵六', age=20, gender=MALE, grade=TWO}]
}
学生班级列表 =
{
ONE = [Student{name='李四', age=8, gender=MALE, grade=ONE}]
FOUR = [Student{name='张三', age=10, gender=FEMALE, grade=FOUR}]
THREE = [Student{name='王五', age=18, gender=MALE, grade=THREE}]
TWO = [Student{name='赵六', age=20, gender=MALE, grade=TWO}]
}
每个班级学生个数列表 =
{
ONE = 1
FOUR = 1
THREE = 1
TWO = 1
}


  

 七、流的运行机制

1、测试代码如下:



public static void main(String[] args) {

Random random = new Random();
//随机产生数据
Stream<Integer> integerStream = Stream.generate(() -> random.nextInt())
// 产生10个(无限流需要短路操作)
.limit(10)
// 第1个无状态操作
.peek(s -> System.out.println("无状态操作1 peek:" + s))
// 第2个无状态操作
.filter(s -> {
System.out.println("无状态操作2 filter: " + s);
return s > 1;
})
});
//终止操作
integerStream.count();
}


输出结果:

 



无状态操作1 peek:958678073
无状态操作2 filter: 958678073
无状态操作1 peek:-1254433080
无状态操作2 filter: -1254433080
无状态操作1 peek:-1544631259
无状态操作2 filter: -1544631259
无状态操作1 peek:-949435277
无状态操作2 filter: -949435277
无状态操作1 peek:-1380758868
无状态操作2 filter: -1380758868
无状态操作1 peek:-437107324
无状态操作2 filter: -437107324
无状态操作1 peek:-617414841
无状态操作2 filter: -617414841
无状态操作1 peek:-685899581
无状态操作2 filter: -685899581
无状态操作1 peek:-568187900
无状态操作2 filter: -568187900
无状态操作1 peek:514107384
无状态操作2 filter: 514107384


 

  

总结:所有操作是链式调用,一个元素只迭代一次

 

2、Debug代码

java 8 Stream流编程_日期格式_05

 

 

 

每一个中间操作返回一个新的流。流里面有一个属性sourceStage 指向同一个地方,就是Head

 

java 8 Stream流编程_格式转换_06

 

 Head-> nextStage -> nextStage ->  ... -> null

这就是实现链式调用的过程

 

3、两个无状态操作后,增加一个有状态,再增加一个无状态操作



public static void main(String[] args) {

Random random = new Random();
//随机产生数据
Stream<Integer> integerStream = Stream.generate(() -> random.nextInt())
// 产生10个(无限流需要短路操作)
.limit(10)
// 第1个无状态操作
.peek(s -> System.out.println("无状态操作1 peek:" + s))
// 第2个无状态操作
.filter(s -> {
System.out.println("无状态操作2 filter: " + s);
return s > 1;
})
//有状态操作
.sorted((i1,i2) -> {
System.out.println("排序:" + i1 + "," +i2);
return i1.compareTo(i2);
})
// 第3个无状态操作
.peek(s -> {
System.out.println("无状态操作3 peek2:" + s);
});
//终止操作
integerStream.count();
}


 

输出结果如下:



无状态操作1 peek:1212039829
无状态操作2 filter: 1212039829
无状态操作1 peek:-824860534
无状态操作2 filter: -824860534
无状态操作1 peek:1647394626
无状态操作2 filter: 1647394626
无状态操作1 peek:-989892865
无状态操作2 filter: -989892865
无状态操作1 peek:1679831720
无状态操作2 filter: 1679831720
无状态操作1 peek:429970807
无状态操作2 filter: 429970807
无状态操作1 peek:-820772493
无状态操作2 filter: -820772493
无状态操作1 peek:-1270158375
无状态操作2 filter: -1270158375
无状态操作1 peek:51717906
无状态操作2 filter: 51717906
无状态操作1 peek:-412303194
无状态操作2 filter: -412303194
排序:1647394626,1212039829
排序:1679831720,1647394626
排序:429970807,1679831720
排序:429970807,1647394626
排序:429970807,1212039829
排序:51717906,1647394626
排序:51717906,1212039829
排序:51717906,429970807
无状态操作3 peek2:51717906
无状态操作3 peek2:429970807
无状态操作3 peek2:1212039829
无状态操作3 peek2:1647394626
无状态操作3 peek2:1679831720


 

总结: 有状态操作会把无状态操作截断,单独处理

 

其它: 1、并行环境下,有状态的中间操作不一定能并行操作

       2、 parallel/sequetial 这2个操作也是中间操作(也是返回stream)

    但是他们不创建流,他们只修改Head的并行标志。