Java 8 如何以并发方式在同一个流 上执行多种操作

本文探讨了在Java 8中如何利用Spliterator和BlockingQueues并发地在同一个流上执行多种操作,以克服流只能操作一次的限制。通过创建ForkingStreamConsumer和BlockingQueueSpliterator,实现了在流上并发执行操作并获取多个结果的功能。虽然这种方法可能不总是性能最佳,但在某些涉及大量I/O的操作中,它可以提高效率。
摘要由CSDN通过智能技术生成

Java 8 中,流有一个非常大的(也可能是最大的)局限性,使用时,对它操作一次仅能得到一个处理结果。实际操作中,如果你试图多次遍历同一个流,结果只有一个,那就是遭遇下面这样的异常:
java.lang.IllegalStateException: stream has already been operated upon or closed
虽然流的设计就是如此,但我们在处理流时经常希望能同时获取多个结果。
本篇利用一个通用API,即Spliterator,尤其是它的延迟绑定能力,结合BlockingQueues和Futures来实现这一大有裨益的特性。

1.复制流

    要达到在一个流上并发地执行多个操作的效果,你需要做的第一件事就是创建一个StreamForker,这个StreamForker会对原始的流进行封装,在此基础之上你可以继续定义你希望执行的各种操作。我们看看下面这段代码。

public class StreamForker<T> {
   

	private final Stream<T> stream;
	private final Map<Object, Function<Stream<T>, ?>> forks = new HashMap<>();

	public StreamForker(Stream<T> stream) {
   
		this.stream = stream;
	}

	/**
	 * 这里的fork方法接受两个参数。
	 * Function参数,它对流进行处理,将流转变为代表这些操作结果的任何类型。
	 * key参数,通过它你可以取得操作的结果,并将这些键/函数对累积到一个内部的Map中。
	 *
	 * @param key
	 * @param f
	 * @return
	 */
	public StreamForker<T> fork(Object key, Function<Stream<T>, ?> f) {
   
		forks.put(key, f);
		return this; //返回this从而保证多次流畅地调用fork方法
	}

	public Results getResults() {
   
	
	ForkingStreamConsumer<T> consumer = build();

		try {
   
			stream.sequential().forEach(consumer);
		} finally {
   
			consumer.finish();
		}

		return consumer;
	}

}

    所有由fork方法添加的操作的执行都是通过getResults方法的调用触发的,该方法返回一个Results接口的实现,具体的定义如下:

	public interface Results {
   

	 <R> R get(Object key);
}
1.1 使用 ForkingStreamConsumer 实现 Results 接口

    你可以用下面的方式实现getResults方法:

	public Results getResults() {
   

		ForkingStreamConsumer<T> consumer = build();

		try {
   
			stream.sequential().forEach(consumer);
		} finally {
   
			consumer.finish();
		}

		return consumer;
	}

     ForkingStreamConsumer同时实现了前面定义的Results接口和Consumer接口。随着我们进一步剖析它的实现细节,你会看到它主要的任务就是处理流中的元素,将它们分发到多个BlockingQueues中处理,BlockingQueues的数量和通过fork方法提交的操作数是一致的。注意,我们很明确地知道流是顺序处理的,不过,如果你在一个并发流上执行forEach方法,它的元素可能就不是顺序地被插入到队列中了。finish方法会在队列的末尾插入特殊元素表明该队列已经没有更多需要处理的元素了。build方法主要用于创建ForkingStreamConsumer。

	private ForkingStreamConsumer<T> build() {
   
		//创建由队列组成的列表,每一个队列对应一个操作
		List<BlockingQueue<T>> queues = new ArrayList<>();

		//建立用于标识操作的键与包含操作结果的Future之间的映射关系
		HashMap<Object, Future<?>> actions = forks.entrySet().stream().reduce(
				new HashMap<>(),
				(map, e) -> {
   
					map.put(e.getKey(), getOperationResult(queues, e.getValue()));
					return map;
				},
				(m1, m2) -> {
   
					m1.putAll
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值