一文带你玩转Java8Stream,从此集合操作SoEasy

一文带你玩转Java8Stream,从此集合操作SoEasy

有需要互关的小伙伴,关注一下,有关必回关,争取今年认证早日拿到博客专家

标签:java基础

不同类型的流

public interface Stream<T> extends BaseStream<T, Stream<T>>
public interface IntStream extends BaseStream<Integer, IntStream>
public interface LongStream extends BaseStream<Long, LongStream>
public interface DoubleStream extends BaseStream<Double, DoubleStream>

上面这些原始类型流的工作方式与常规对象流基本是一样的,但还是略微存在一些区别:

  • 原始类型流使用其独有的函数式接口,例如IntFunction代替FunctionIntPredicate代替Predicate
  • 原始类型流支持额外的终端聚合操作,sum()以及average(),如下所示:
Arrays.stream(new int[] {1, 2, 3}).average().ifPresent(System.out::println);

但是,偶尔我们也有这种需求,需要将常规对象流转换为原始类型流,这个时候,中间操作 mapToInt()mapToLong() 以及mapToDouble就派上用场了

List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
persons.stream().mapToInt(p->p.age).max().ifPresent(System.out::println);
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
23

如果说,您需要将原始类型流装换成对象流,您可以使用 mapToObj()来达到目的:

IntStream.range(1,4).mapToObj(String::valueOf).forEach(System.out::println);

中间操作与终端操作

中间操作返回Stream,终端操作返回void或者非stream

没有终端操作,中间操作是不会生效的.

/**
 * 什么都不会输出,why
 * 因为只有存在终端操作时中间操作才会被执行
 */
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> {
			System.out.println("filter: " + s);
			return true;
		});

Stream 流的处理顺序

/**
 * sorted 水平调用(需要保存中间状态) 在水平调用前的所有垂直调用将变为水平调用
 * filter,map,垂直执行(提高性能)
 * 有状态的操作 distinct skip limit sorted reduce
 */
Stream.of("d2", "a2", "b1", "b3", "c")
		.map(s -> {
			System.out.println("map1: " + s);
			return s.toUpperCase(); // 转大写
		})
		.sorted((s1, s2) -> {
			System.out.printf("sort: %s; %s\n", s1, s2);
			return s1.compareTo(s2); // 排序
		})
		.map(s -> {
			System.out.println("map2: " + s);
			return s.toLowerCase();
		})
		.filter(s -> {
			System.out.println("filter: " + s);
			return s.startsWith("a"); // 过滤出以 a 为前缀的元素
		})
		.forEach(s -> System.out.println("forEach: " + s)); // for 循环输出
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> {
            System.out.println("filter: " + s);
            return true;
        });

执行此代码段时,您可能会认为,将依次打印 “d2”, “a2”, “b1”, “b3”, “c” 元素。然而当你实际去执行的时候,它不会打印任何内容。

为什么呢?

原因是:当且仅当存在终端操作时,中间操作操作才会被执行。

数据流复用问题

Java8 Stream 流是不能被复用的,一旦你调用任何终端操作,流就会关闭:

Stream<String> stream = Stream.of("d2", "a2", "b1", "b3", "c");
// 终端操作会关闭流
boolean b = stream.anyMatch(item -> true);
Optional<String> any = stream.findAny();
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
	at java.util.stream.ReferencePipeline.findAny(ReferencePipeline.java:469)
	at StreamDemo.extracted7(StreamDemo.java:95)
	at StreamDemo.main(StreamDemo.java:20)

为了克服这个限制,我们必须为我们想要执行的每个终端操作创建一个新的流链,例如,我们可以通过 Supplier 来包装一下流,通过 get() 方法来构建一个新的 Stream 流,如下所示:

Supplier<Stream<String>> supplier = () -> Stream.of("d2", "a2", "b1", "b3", "c");
// 终端操作会关闭流 流的复用
boolean b = supplier.get().anyMatch(item -> true);
Optional<String> any = supplier.get().findAny();

通过构造一个新的流,来避开流不能被复用的限制, 这也是取巧的一种方式。

Stream常用api

中间操作

distinct

skip与limit

List<String> list = getList();
int pageSizse = 10;
int total = list.size() / pageSizse;
if (list.size() % pageSizse != 0) {
    total += 1;
}
for (int i = 0; i < total; i++) {
    System.out.println(String.format("第%d页", i + 1));
    list.stream().skip(i * pageSizse).limit(pageSizse).forEach(item -> System.out.println("item = " + item));
}

filter

public static void main(String[] args) {
    List<String> xxx = new ArrayList<>();
    xxx.add("zss");
    List<String> yyy = xxx.stream().filter(item -> item.equals("yyy")).collect(Collectors.toList());
    System.out.println(yyy.size());
}

filter没有元素返回空集合,不会返回null

peek

peek 窥视 偷看

List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// peek 改变元素的内部状态(如果没有终端操作,这里是不会触发的) 可以这么用,但不建议
persons.stream().peek(p->p.age = p.age*2).forEach(System.out::println);
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=11}
{name='Max', age=36}
{name='Peter', age=46}
{name='Pamela', age=46}
{name='David', age=22

peek() vs forEach()

forEach() 是一个最终操作。除此之外,peek()forEach() 再无其他不同。

peek() 的典型用法:协助调试

List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
persons.stream().filter(p -> p.age >= 18).peek(System.out::println).forEach(p->{
    p.age=p.age*2;
    System.out.println("p = " + p);
});

小结

  • 如果想对流经的每个元素应用一个函数,从而改变某些状态,那么请用 forEach()
  • 如果想打印流经的每个元素的状态(日志或 debug),这时应该用 peek()

map

mapToInt

mapToLong

mapToDouble

sorted

FlatMap

@Data
class Foo {
    private String name;
    private List<Bar> bars = new ArrayList<>();

    public Foo(String name) {
        this.name = name;
    }
}

@Data
class Bar {
    private String name;

    public Bar(String name) {
        this.name = name;
    }
}
List<Foo> foos = new ArrayList<>();
for (int i = 1; i < 4; i++) {
    Foo foo = new Foo("Foo" + i);
    List<Bar> bars = new ArrayList<>();
    for (int j = 1; j < 4; j++) {
        bars.add(new Bar("Bar"+j+"<-"+foo.getName()));
    }
    foo.setBars(bars);
    foos.add(foo);
}
for (Foo foo : foos) {
    System.out.println("foo = " + foo);
}
foos.stream().flatMap(foo -> foo.getBars().stream()).forEach(bar-> System.out.println(bar.getName()));
foo = Foo(name=Foo1, bars=[Bar(name=Bar1<-Foo1), Bar(name=Bar2<-Foo1), Bar(name=Bar3<-Foo1)])
foo = Foo(name=Foo2, bars=[Bar(name=Bar1<-Foo2), Bar(name=Bar2<-Foo2), Bar(name=Bar3<-Foo2)])
foo = Foo(name=Foo3, bars=[Bar(name=Bar1<-Foo3), Bar(name=Bar2<-Foo3), Bar(name=Bar3<-Foo3)])
Bar1<-Foo1
Bar2<-Foo1
Bar3<-Foo1
Bar1<-Foo2
Bar2<-Foo2
Bar3<-Foo2
Bar1<-Foo3
Bar2<-Foo3
Bar3<-Foo3

Optional的flatMap

Outer outer = new Outer();
Nested nested = new Nested();
outer.nested = nested;
Inner inner = new Inner();
nested.inner = inner;
inner.foo = "foo...";

if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}

取代

Outer outer = new Outer();
Nested nested = new Nested();
outer.nested = nested;
Inner inner = new Inner();
nested.inner = inner;
inner.foo = "foo...";

Optional.of(outer)
        .flatMap(o -> Optional.ofNullable(o.nested))
        .flatMap(n -> Optional.ofNullable(n.inner))
        .flatMap(i -> Optional.ofNullable(i.foo))
        .ifPresent(System.out::println);
List<EmergencyIot> compCode1 = detectionIots.stream().flatMap(item -> Optional.ofNullable(item.getEmergencyIots()).orElse(Collections.emptyList()).stream()).peek(item -> item.setCompCode("compCode")).collect(Collectors.toList());
List<EmergencyIot> compCode1 = detectionIots.stream().flatMap(item -> Optional.ofNullable(item.getEmergencyIots()).orElse(Collections.emptyList()).stream()).peek(item -> item.setCompCode("compCode")).collect(Collectors.toList());

终端操作

forEach与forEachOrdered

List<String> strs = Arrays.asList("a", "b", "c");
strs.stream().map(String::toUpperCase).forEachOrdered(System.out::print);
System.out.println();
strs.stream().map(String::toUpperCase).forEach(System.out::print);
System.out.println();
strs.parallelStream().forEachOrdered(System.out::print);
System.out.println();
strs.parallelStream().forEach(System.out::print);
ABC
ABC
abc
bac

猜测:forEach与forEachOrdered在串行流下没区别

并行流parallelStream下forEachOrdered严格按照顺序(效率低),forEach与线程执行速度有关(效率高)

count

findAny、findFirst 、max与min

findAny(找到并返回任何一个(都行))、findFirst 、max与min返回值都是Optional

List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// 在串行时findAny返回的是第一个元素与findFirst效果一样,在并行时返回的是处理最快的线程的结果,效率上比findFirst快
persons.stream().findAny().ifPresent(System.out::println);
persons.stream().findFirst().ifPresent(System.out::println);
persons.stream().max(Comparator.comparingInt(p -> p.age)).ifPresent(System.out::println);
persons.stream().min(Comparator.comparingInt(p -> p.age)).ifPresent(System.out::println);
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
{name='Max', age=18}
{name='Max', age=18}
{name='Peter', age=23}
{name='David', age=12}

anyMatch、allMatch与noneMatch

anyMatch、allMatch与noneMatch的返回值都是boolean

List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// anyMatch 只要匹配到任意一个就停止并返回true匹配到就停止
System.out.println(persons.stream().anyMatch(item -> item.age == 18));
// 都是则返回true
System.out.println(persons.stream().allMatch(item -> item.age == 18));
// 都不是则返回true
System.out.println(persons.stream().noneMatch(item -> item.age == 18));
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
true
false
false

toArray

Object[] listToArrayobjects = persons.toArray();
System.out.println("listToArrayobjects = " + Arrays.toString(listToArrayobjects));
Person[] listToArrayPersons = persons.toArray(new Person[0]);
System.out.println("listToArrayPersons = " + Arrays.toString(listToArrayPersons));
Object[] streamToArrayObjects = persons.stream().filter(p -> p.age > 18).toArray();
System.out.println("streamToArrayObjects = " + Arrays.toString(streamToArrayObjects));
Person[] streamToArrayPersons = persons.stream().filter(p -> p.age > 18).toArray(Person[]::new);
System.out.println("streamToArrayPersons = " + Arrays.toString(streamToArrayPersons));
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
listToArrayobjects = [{name='Max', age=18}, {name='Peter', age=23}, {name='Pamela', age=23}, {name='David', age=12}]
listToArrayPersons = [{name='Max', age=18}, {name='Peter', age=23}, {name='Pamela', age=23}, {name='David', age=12}]
streamToArrayObjects = [{name='Peter', age=23}, {name='Pamela', age=23}]
streamToArrayPersons = [{name='Peter', age=23}, {name='Pamela', age=23}]

注意:使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组(阿里开发手册)

直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。

说明:使用 toArray 带参方法,数组空间大小的 length:
1) 等于 0,动态创建与 size 相同的数组,性能最好。
2) 大于 0 但小于 size,重新创建大小等于 size 的数组,增加 GC 负担。
3) 等于 size,在高并发情况下,数组创建完成之后,size 正在变大的情况下,负面影响与上相同。
4) 大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患。

reduce

规约操作可以将流的所有元素组规约一个结果。Java 8 支持三种不同的reduce方法。第一种将流中的元素规约成流中的一个元素。

找出年龄最大的人

List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
persons.stream().reduce((p1,p2)->p1.age>p2.age?p1:p2).ifPresent(System.out::println);
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
{name='Pamela', age=23}

第二种reduce方法接受标识值和BinaryOperator累加器。此方法可用于构造一个新的 Person,其中包含来自流中所有其他人的聚合名称和年龄:

List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
Person result = persons.stream().reduce(new Person("", 0), (p1, p2) -> {
                    p1.age += p2.age;
                    p1.name += p2.name;
                    return p1;
                });
System.out.format("name=%s; age=%s", result.name, result.age);
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
name=MaxPeterPamelaDavid; age=76

第三种reduce方法接受三个参数:标识值,BiFunction累加器和类型的组合器函数BinaryOperator。由于初始值的类型不一定为Person,我们可以使用这个归约函数来计算所有人的年龄总和:

List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
Integer ageSum = persons.stream().reduce(0, (sum, p) -> sum += p.age, Integer::sum);
System.out.println(ageSum);
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
76

结果为76,但是内部究竟发生了什么呢?让我们再打印一些调试日志:

Integer ageSum = persons.stream().reduce(0,
		(sum, p) -> {
			System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
			return sum += p.age;
		},
		(sum1, sum2) -> {
			System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);
			return sum1 + sum2;
		});
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
accumulator: sum=0; person={name='Max', age=18}
accumulator: sum=18; person={name='Peter', age=23}
accumulator: sum=41; person={name='Pamela', age=23}
accumulator: sum=64; person={name='David', age=12}

你可以看到,累加器函数完成了所有工作。它首先使用初始值0和第一个人年龄相加。接下来的三步中sum会持续增加,直到76。

等等?好像哪里不太对!组合器从来都没有调用过啊?

我们以并行流的方式运行上面的代码,看看日志输出:

Integer ageSum = persons.parallelStream().reduce(0,
		(sum, p) -> {
			System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
			return sum += p.age;
		},
		(sum1, sum2) -> {
			System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);
			return sum1 + sum2;
		});
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
accumulator: sum=0; person={name='Max', age=18}
accumulator: sum=0; person={name='Peter', age=23}
combiner: sum1=18; sum2=23
accumulator: sum=0; person={name='Pamela', age=23}
accumulator: sum=0; person={name='David', age=12}
combiner: sum1=23; sum2=12
combiner: sum1=41; sum2=35

并行流的执行方式完全不同。这里组合器被调用了。实际上,由于累加器被并行调用,组合器需要被用于计算部分累加值的总和。

collect

常见收集器

数据源

private static List<Person> getPersons() {
    List<Person> persons = Arrays.asList(new Person("Max", 18),
                                         new Person("Peter", 23),
                                         new Person("Pamela", 23),
                                         new Person("David", 12));
    return persons;
}

Collectors.toList()

Collectors.toSet()

Collectors.toMap

如果有两个相同的key会报错java.lang.IllegalStateException: Duplicate key Peter

源码

public static <T, K, U > Collector < T, ?,Map<K, U>>toMap(
    Function < ? super T, ? extends K > keyMapper,
    Function < ? super T, ? extends U > valueMapper){
    return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

测试代码

// [{name='Max', age=18}, {name='Peter', age=23}, {name='Pamela', age=23}, {name='David', age=12}]
List<Person> persons = getPersons();
// 出现两个相同的key,报错
Map<Integer, String> map = persons.stream().collect(Collectors.toMap(p -> p.age, p -> p.name)); 

出现两个相同的key,将value合并

源码

public static <T, K, U > Collector < T, ?,Map<K, U>>toMap(
    Function < ? super T, ? extends K > keyMapper,
    Function < ? super T, ? extends U > valueMapper,
    BinaryOperator < U > mergeFunction){
    return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}

测试代码

BinaryOperator<String> mergeFunction = (value1, value2) -> {
    if (value1.equals(value2)) {
        return value1;
    }
    return value1 + ";" + value2;
};
Map<Integer, String> map = persons.stream().collect(Collectors.toMap(p -> p.age, p -> p.name, mergeFunction));
System.out.println(map);
// {18=Max, 23=Peter;Pamela, 12=David}

Collectors.groupingBy

List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// 分组
Map<Integer, List<Person>> groupByPersons = persons.stream().collect(Collectors.groupingBy(p -> p.age));
System.out.println("groupingByPersons = " + groupByPersons);
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=11}
groupingByPersons = {18=[{name='Max', age=18}], 23=[{name='Peter', age=23}, {name='Pamela', age=23}], 11=[{name='David', age=11}]}

Collectors.partitioningBy

List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// 分区
Map<Boolean, List<Person>> partitioningByPersons = persons.stream().collect(Collectors.partitioningBy(p -> p.age >= 18));
System.out.println("partitioningByPersons = " + partitioningByPersons);
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=11}
partitioningByPersons = {false=[{name='David', age=11}], true=[{name='Max', age=18}, {name='Peter', age=23}, {name='Pamela', age=23}]}

Collectors.summarizingInt

List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// 综合统计age属性 count max min sum average
IntSummaryStatistics statistics = persons.stream().collect(Collectors.summarizingInt(p -> p.age));
System.out.println("statistics = " + statistics);
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=11}
statistics = IntSummaryStatistics{count=4, sum=75, min=11, average=18.750000, max=23}
count = 4
max = 23
min = 11
sum = 75
average = 18.75

Collectors.joining

如果能用

/**
 * Can be replaced with 'String.join'
 * 如果能用String.join则优先使用String.join 例如字符串数组或字符串集合
 * 如果不能用String.join再用Collectors.joining(),例如对象的某个属性拼接(需要先map成字符串,然后收集)
 */
String collect = String.join("", list);
// Can be replaced with 'String.join'
collect = list.stream().collect(Collectors.joining());
System.out.println("collect = " + collect);
// Can be replaced with 'String.join'
collect = list.stream().collect(Collectors.joining("and"));
System.out.println("collect = " + collect);
// 底层实现StringJoiner,StringJoiner的底层实现StringBuilder
System.out.println("collect = " + collect);
String collect3 = list.stream().collect(Collectors.joining("and", "start", "end"));
System.out.println("collect3 = " + collect3);

其他

‘collect(summingInt())’ can be replaced with ‘mapToInt().sum()’

对age属性求和或者平均数,可以先用mapToInt成IntStream,然后调用IntStream的sum,average方法而非收集器的方法

最大值Collectors.maxBy 最小值Collectors.minBy 规约Collectors.reducing 均推荐用stream的max,min,reduce

自定义收集器Collector

一个Collector是由四部分组成的:

  • Supplier supplier(): 创建新的结果容器

  • BiConsumer<A, T> accumulator(): 将元素添加到结果容器

  • BinaryOperator combiner(): 将两个结果容器合并为一个结果容器

  • Function<A, R> finisher(): 对结果容器作相应的变换

Collector自定义起来,也不是特别的麻烦,不过要明确以下几点:

  1. 参数类型:

    待收集元素的类型:T

    累加器的类型:A

    最终结果的类型:R

  2. 累加器的逻辑

  3. 最终结果的转换

  4. Collector特征的选择

现在有个简单的需求,求一段数字的和,如果是奇数,直接相加;如果是偶数,乘以2后在相加。这样的场景下,Collector类库中的收集器不能满足我们的需求,我们只能够自己定义了。

class IntegerSum {
    int sum;

    public IntegerSum doSum(Integer item) {
        if (item % 2 == 0) {
            this.sum += item * 2;
        } else {
            this.sum += item;
        }
        return this;

    }

    public IntegerSum doCombine(IntegerSum it) {
        this.sum += it.sum;
        return this;
    }

    public Integer toValue() {
        return this.sum;
    }

}
public static void main(String[] args) {
    // 1.创建新的结果容器
    Supplier<IntegerSum> supplier = IntegerSum::new;
    // 2.将元素添加到结果容器
    BiConsumer<IntegerSum, Integer> accumulator = IntegerSum::doSum;
    // 3.合并多个结果容器
    BinaryOperator<IntegerSum> combiner = IntegerSum::doCombine;
    // 4.最终结果的转换
    Function<IntegerSum, Integer> finisher = IntegerSum::toValue;
    /**
     * 泛型说明
     * T 待收集元素的类型
     * A 累加器的类型
     * R 最终结果的类型
     */
    Collector<Integer, IntegerSum, Integer> collector = Collector.of(supplier, accumulator, combiner, finisher);
    Integer result = Stream.of(1, 3, 5, 7).collect(collector);
    System.out.println("result = " + result);
}
/**
 * 泛型说明
 * T 待收集元素的类型
 * A 累加器的类型
 * R 最终结果的类型
 */
Collector<Integer, IntegerSum, Integer> collector = new Collector<Integer, IntegerSum, Integer>() {
    @Override
    public Supplier<IntegerSum> supplier() {
        return IntegerSum::new;
    }

    @Override
    public BiConsumer<IntegerSum, Integer> accumulator() {
        return IntegerSum::doSum;
    }

    @Override
    public BinaryOperator<IntegerSum> combiner() {
        return IntegerSum::doCombine;
    }

    @Override
    public Function<IntegerSum, Integer> finisher() {
        return IntegerSum::toValue;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.emptySet();
    }
};
Integer result = Stream.of(1, 3, 5, 7).collect(collector);
System.out.println("result = " + result);

描述收集器的特征: enum Characteristics

并行流

流是可以并行执行的,当流中存在大量元素时,可以显著提升性能。并行流底层使用的ForkJoinPool, 它由ForkJoinPool.commonPool()方法提供。底层线程池的大小取决于 CPU 可用核心数:

ForkJoinPool commonPool = ForkJoinPool.commonPool();
System.out.println(commonPool.getParallelism());    // 11

在我的机器上,公共池初始化默认值为 3。你也可以通过设置以下JVM参数可以减小或增加此值:

-Djava.util.concurrent.ForkJoinPool.common.parallelism=5

集合支持parallelStream()方法来创建元素的并行流。或者你可以在已存在的数据流上调用中间方法parallel(),将串行流转换为并行流,这也是可以的。

Arrays.asList("a1", "a2", "b1", "c2", "c1").parallelStream()
		.filter(s -> {
			System.out.format("filter: %s [%s]\n", s, Thread.currentThread().getName());
			return true;
		})
		.map(s -> {
			System.out.format("map: %s [%s]\n", s, Thread.currentThread().getName());
			return s.toUpperCase();
		})
		.sorted((s1, s2) -> {
			System.out.format("sort: %s <> %s [%s]\n", s1, s2, Thread.currentThread().getName());
			return s1.compareTo(s2);
		})
		.forEach(s -> System.out.format("forEach: %s [%s]\n", s, Thread.currentThread().getName()));
filter: b1 [main]
filter: c2 [ForkJoinPool.commonPool-worker-4]
map: c2 [ForkJoinPool.commonPool-worker-4]
filter: c1 [ForkJoinPool.commonPool-worker-11]
map: c1 [ForkJoinPool.commonPool-worker-11]
filter: a2 [ForkJoinPool.commonPool-worker-9]
map: a2 [ForkJoinPool.commonPool-worker-9]
filter: a1 [ForkJoinPool.commonPool-worker-2]
map: a1 [ForkJoinPool.commonPool-worker-2]
map: b1 [main]
sort: A2 <> A1 [main]
sort: B1 <> A2 [main]
sort: C2 <> B1 [main]
sort: C1 <> C2 [main]
sort: C1 <> B1 [main]
sort: C1 <> C2 [main]
forEach: B1 [main]
forEach: C1 [ForkJoinPool.commonPool-worker-9]
forEach: A1 [ForkJoinPool.commonPool-worker-11]
forEach: C2 [ForkJoinPool.commonPool-worker-4]
forEach: A2 [ForkJoinPool.commonPool-worker-2]

貌似sort只在主线程上串行执行。但是实际上,并行流中的sort在底层使用了Java8中新的方法Arrays.parallelSort()。如 javadoc官方文档解释的,这个方法会按照数据长度来决定以串行方式,或者以并行的方式来执行。

如果指定数据的长度小于最小数值,它则使用相应的Arrays.sort方法来进行排序。

总之,你需要记住的是,并行流对含有大量元素的数据流提升性能极大。但是你也需要记住并行流的一些操作,例如reducecollect操作,需要额外的计算(如组合操作),这在串行执行时是并不需要。

此外,我们也了解了,所有并行流操作都共享相同的 JVM 相关的公共ForkJoinPool。所以你可能需要避免写出一些又慢又卡的流式操作,这很有可能会拖慢你应用中,严重依赖并行流的其它部分代码的性能。

作者:犬小哈
链接:https://juejin.cn/post/6844903830254010381
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spark是一个快速通用的集群计算框架,它可以处理大规模数据,并且具有高效的内存计算能力。Spark可以用于各种计算任务,包括批处理、流处理、机器学习等。本文将你了解Spark计算框架的基本概念和使用方法。 一、Spark基础概念 1. RDD RDD(Resilient Distributed Datasets)是Spark的基本数据结构,它是一个分布式的、可容错的、不可变的数据集合。RDD可以从Hadoop、本地文件系统等数据源中读取数据,并且可以通过多个转换操作(如map、filter、reduce等)进行处理。RDD也可以被持久化到内存中,以便下次使用。 2. Spark应用程序 Spark应用程序是由一个驱动程序和多个执行程序组成的分布式计算应用程序。驱动程序是应用程序的主要入口点,它通常位于用户的本地计算机上,驱动程序负责将应用程序分发到执行程序上并收集结果。执行程序是运行在集群节点上的计算单元,它们负责执行驱动程序分配给它们的任务。 3. Spark集群管理器 Spark集群管理器负责管理Spark应用程序在集群中的运行。Spark支持多种集群管理器,包括Standalone、YARN、Mesos等。 二、Spark计算框架使用方法 1. 安装Spark 首先需要安装Spark,可以从Spark官网下载并解压缩Spark安装包。 2. 编写Spark应用程序 编写Spark应用程序通常需要使用Java、Scala或Python编程语言。以下是一个简单的Java代码示例,用于统计文本文件中单词的出现次数: ```java import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import java.util.Arrays; import java.util.Map; public class WordCount { public static void main(String[] args) { SparkConf conf = new SparkConf().setAppName("WordCount").setMaster("local"); JavaSparkContext sc = new JavaSparkContext(conf); JavaRDD<String> lines = sc.textFile("input.txt"); JavaRDD<String> words = lines.flatMap(line -> Arrays.asList(line.split(" ")).iterator()); Map<String, Long> wordCounts = words.countByValue(); for (Map.Entry<String, Long> entry : wordCounts.entrySet()) { System.out.println(entry.getKey() + " : " + entry.getValue()); } sc.stop(); } } ``` 3. 运行Spark应用程序 将编写好的Spark应用程序打包成jar包,并通过以下命令运行: ```bash spark-submit --class WordCount /path/to/wordcount.jar input.txt ``` 其中,--class参数指定应用程序的主类,后面跟上打包好的jar包路径,input.txt是输入文件的路径。 4. 查看运行结果 Spark应用程序运行完毕后,可以查看应用程序的输出结果,例如上述示例中的单词出现次数。 以上就是Spark计算框架的基本概念和使用方法。通过学习Spark,我们可以更好地处理大规模数据,并且提高计算效率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值