JDK 提供的Collectors中有大部分常用的, 但是有些特殊情况需要自己自定义收集器, 以下就简单介绍一下自定义收集器的写法
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
/**
* @Description: <br/>
* Collector<T, A, R>
* T: 表示流元素的类型
* A: 收集器类型
* R: 结果类型
* --------------------------------
* 把一个字符串流, 收集为一个Map key/value 都为流中的元素
* ["hello", "world"] -> [{ "hello": "hello"},{ "world": "world"}]
* <p>
* <br/>
* @Author: Qz1997
* @create 2020/12/7 19:35
*/
public class MySetToMapCollector<T extends String> implements Collector<T, Set<T>, Map<T, T>> {
/**
* 创建并返回新的可变结果收集器的函数
* 注: 如果是串行流的话或者并行流 characteristics() 方法返回的set中有Characteristics.CONCURRENT
* 那个就只会有一个结果容器
*
* @return 创建一个收集器类型
*/
@Override
public Supplier<Set<T>> supplier() {
System.out.println("MySetCollector.supplier");
///return HashSet::new;
return () -> {
System.out.println("----------supplier.lambda执行了-----------");
return new HashSet<>();
};
}
/**
* 累加器
* 不断的将流中的元素 累加到收集器中
* 就是讲T 累加到 A中
*
* @return 不断的将流中的元素 累加到收集器中的函数
*/
@Override
public BiConsumer<Set<T>, T> accumulator() {
System.out.println("MySetCollector.accumulator");
return (set, item) -> {
System.out.println("result: " + set + " accumulator.for: " + Thread.currentThread().getName());
set.add(item);
};
}
/**
* 用于并发流 讲多个线程的结果而合并成一个
* 注意:
* combiner() 方法调用的前提是调用的是并行流parallelStream
* characteristics() 方法返回的set中没有Characteristics.CONCURRENT 该方法会被执行
*
* @return 合并的结果函数
*/
@Override
public BinaryOperator<Set<T>> combiner() {
System.out.println("MySetCollector.combiner");
return (set1, set2) -> {
System.out.println("set1 = " + set1);
System.out.println("set2 = " + set2);
set1.addAll(set2);
return set1;
};
}
/**
* 转换器
* 这个是可选的
* 这个方法就是将收集器转换成最终的结果类型
* 对于大多数使用collector来说 收集器类型就是最终的结果类型
* characteristics() 方法返回的set中没有IDENTITY_FINISH 该方法会被执行
*
* @return result
*/
@Override
public Function<Set<T>, Map<T, T>> finisher() {
System.out.println("MySetCollector.finisher");
return (set) -> {
Map<T, T> map = new HashMap<>(16);
set.forEach(o -> map.put(o, o));
return map;
};
}
/**
* 表示这个收集器的特征
* Characteristics
* CONCURRENT:
* 这个表示一个结果容器可以到个线程调用, 要保证线程安全 他和并行流是不一样的 如果这个收集器不是UNORDERED 那么仅能用于无序的数据源
* UNORDERED:
* 他并不保证输入元素的顺序 也就是无序的
* IDENTITY_FINISH:
* 标识的是: finisher 就是 identity函数 就是收集器的的类型和结果的类型相同 但是必须要保证 A 转换成 R 类型是成功的
* ---------------------------------
* 如果不满足以上三个条件 那么就直接返回一个空的set集合就可以了
*
* @return result
*/
@Override
public Set<Characteristics> characteristics() {
System.out.println("MySetCollector.characteristics");
// 这是一个不可变的集合
return Collections.unmodifiableSet(EnumSet.of(Characteristics.UNORDERED));
/// return Collections.unmodifiableSet(EnumSet.of(Characteristics.UNORDERED, Characteristics.CONCURRENT));
}
// ------------------------------测试代码-----------------------------
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "welcome", "a", "as", "s", "f", "g", "j", "k", "l", "p", "1", "2");
Set<String> set = new HashSet<>(list);
/// Map<String, String> collect = set.stream().collect(new MySetToMapCollector<>());
/*
* 如果使用parallelStream(并发流) characteristics() 方法 声明了 CONCURRENT 那么 accumulator() 方法中 有两个线程 一个在向结果中添加元素
* 别一个线程 在遍历 就会出现 java.util.ConcurrentModificationException
* 但是如果不加 CONCURRENT 就不会出现这个异常 因为如果加了 那么这个收集器的结果容器就会变成一个 那么这是就有可能出现 一个线程向结果容器中添加元素
* 别一个线程在遍历结果容器
* 不加的话一个线程一个结果容器 这样就永远不会出现 这种异常的情况 我们可以从结果容器中也可以看出
*
*
* 加CONCURRENT:
* 加CONCURRENT的话他的结果容器都很长 说明所用线程使用的是同一个结果容器
* 加CONCURRENT combiner() 方法就不会执行 因为只有一个结果容器 所以就不需要 调用 combiner() 方法 来合并结果容器
* 加CONCURRENT supplier() 方法就会执行一次 所以只有一个结果容器
*
* 打印的结果:
* MySetCollector.characteristics
* MySetCollector.supplier
* ----------supplier.lambda执行了-----------
* MySetCollector.accumulator
* result: [] accumulator.for: main
* result: [p] accumulator.for: main
* result: [p] accumulator.for: ForkJoinPool.commonPool-worker-2
* result: [p, 1] accumulator.for: main
* result: [p, 1, welcome] accumulator.for: ForkJoinPool.commonPool-worker-2
* result: [p, 1, as, welcome] accumulator.for: ForkJoinPool.commonPool-worker-4
* result: [p, 1, as, welcome] accumulator.for: ForkJoinPool.commonPool-worker-9
* result: [p, 1, as, hello, l, welcome] accumulator.for: ForkJoinPool.commonPool-worker-11
* result: [p, 1, as, welcome] accumulator.for: main
* result: [p, 1, as, f, j, hello, l, welcome] accumulator.for: ForkJoinPool.commonPool-worker-13
* result: [p, 1, as, 2, f, j, hello, l, welcome] accumulator.for: main
* result: [p, 1, a, as, 2, world, f, j, hello, l, welcome] accumulator.for: main
* result: [p, 1, as, f, j, hello, l, welcome] accumulator.for: ForkJoinPool.commonPool-worker-11
* result: [p, 1, as, j, hello, l, welcome] accumulator.for: ForkJoinPool.commonPool-worker-9
* MySetCollector.characteristics
* MySetCollector.finisher
* {a=a, f=f, g=g, j=j, k=k, l=l, p=p, 1=1, as=as, 2=2, world=world, s=s, hello=hello, welcome=welcome}
* =++++++++++++++++++++++++
* MySetCollector.characteristics
* MySetCollector.finisher
* ------------------分割线----------------------
*
* 不加CONCURRENT:
* 不加CONCURRENT的话他的结果容器都很短 说明所用每个线程使用都有自己的结果容器
* 不加CONCURRENT combiner() 方法就会执行 因为一个线程一个结果容器 所以需要调用 combiner() 方法来合并结果容器
* 不加CONCURRENT supplier() 方法会多次执行 所以拥有多个结果容器
*
* 打印的结果
* MySetCollector.characteristics
* MySetCollector.supplier
* MySetCollector.accumulator
* MySetCollector.combiner
* MySetCollector.characteristics
* ----------supplier.lambda执行了-----------
* ----------supplier.lambda执行了-----------
* ----------supplier.lambda执行了-----------
* ----------supplier.lambda执行了-----------
* ----------supplier.lambda执行了-----------
* ----------supplier.lambda执行了-----------
* ----------supplier.lambda执行了-----------
* ----------supplier.lambda执行了-----------
* result: [] accumulator.for: ForkJoinPool.commonPool-worker-11
* result: [] accumulator.for: ForkJoinPool.commonPool-worker-13
* result: [] accumulator.for: ForkJoinPool.commonPool-worker-6
* result: [] accumulator.for: main
* result: [] accumulator.for: ForkJoinPool.commonPool-worker-2
* result: [] accumulator.for: ForkJoinPool.commonPool-worker-4
* result: [p] accumulator.for: main
* result: [f] accumulator.for: ForkJoinPool.commonPool-worker-11
* result: [] accumulator.for: ForkJoinPool.commonPool-worker-9
* set1 = [a]
* result: [p, 1] accumulator.for: main
* set1 = [hello]
* result: [p, 1, as] accumulator.for: main
* set2 = [f, g]
* result: [j] accumulator.for: ForkJoinPool.commonPool-worker-9
* result: [p, 1, as, 2] accumulator.for: main
* set2 = [welcome]
* result: [p, 1, as, 2, world] accumulator.for: main
* set1 = [j, k]
* set1 = [p, 1, as, 2, world, s]
* set2 = []
* set2 = [l]
* set1 = [p, 1, as, 2, world, s]
* set1 = [a, f, g]
* set2 = [hello, welcome]
* set2 = [j, k, l]
* set1 = [a, f, g, j, k, l]
* set2 = [p, 1, as, 2, world, s, hello, welcome]
* MySetCollector.characteristics
* MySetCollector.finisher
* {a=a, f=f, g=g, j=j, k=k, l=l, p=p, 1=1, as=as, 2=2, world=world, s=s, hello=hello, welcome=welcome}
* =++++++++++++++++++++++++
* ------------------分割线----------------------
*
* 使用parallelStream(并发流) 他的初始线程数 默认是当前CPU 的超线程数
* 查看方法
* int i = Runtime.getRuntime().availableProcessors();
*
* 修改parallelStream(并发流)初始的线程数 System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "64");
*
* 注意:
* 1.parallelStream线程不安全问题(加锁、使用线程安全的集合或者集合采用collect()或reduce()操作就是满足线程安全的;
* 2.parallelStream 适用的场景是CPU密集型的,假如本身电脑CPU的负载很大,那还到处用并行流,那并不能起到作用,切记不要再paralelSreram操作是中使用IO流;
* 3.不要在多线程中使用parallelStream,原因同上类似,大家都抢着CPU是没有提升效果,反而还会加大线程切换开销;
* 4. 初始的线程数一般不建议修改
*/
int i = Runtime.getRuntime().availableProcessors();
System.out.println("i = " + i);
Map<String, String> collect = set.parallelStream().collect(new MySetToMapCollector<>());
///这种写法等价于上面的写法 collect = set.stream().parallel().collect(new MySetToMapCollector<>());
// collect = set.stream().sequential().collect(new MySetToMapCollector<>()); 这种写法等价串行 collect = set.stream().collect(new MySetToMapCollector<>());
System.out.println(collect);
System.out.println(" =++++++++++++++++++++++++ ");
}