最近项目使用的Stream越来越多,有事想要遍历一遍流的的情况下执行多种操作,输出结果。但是Strem不太支持。下面以StackOverFlow上的一个方案参考实现遍历一遍流的情况下输出多种结果。
1.复制流
/**
* 将同一个流进行合并并发处理了
*
* @author houxiurong
* @date 2019-10-02
*/
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;
}
public StreamForker<T> fork(Object key, Function<Stream<T>, ?> function) {
forks.put(key, function);
return this;
}
public Results getResults() {
ForkingStreamConsumer<T> consumer = builder();
try {
stream.sequential().forEach(consumer);
} finally {
consumer.finish();
}
return consumer;
}
/**
* builder方法创建ForkingStreamConsumer
*
* @return
*/
private ForkingStreamConsumer<T> builder() {
//创建由队列组成的列表,每一个队列对应一个lambda操作
List<BlockingQueue<T>> queues = new ArrayList<>();
//建立用户标识操作的键与包含操作结果的Future之间的映射关系
Map<Object, Future<?>> actions = forks.entrySet().stream()
.reduce(new HashMap<Object, Future<?>>(),
(map, e) -> {
map.put(e.getKey(), getOperationsResult(queues, e.getValue()));
return map;
},
(m1, m2) -> {
m1.putAll(m2);
return m1;
});
return new ForkingStreamConsumer<>(queues, actions);
}
/**
* 使用getOperationsResult创建一个Future
*
* @param queues List队列
* @param func 函数流
* @return 新的Future
*/
private Future<?> getOperationsResult(List<BlockingQueue<T>> queues,
Function<Stream<T>, ?> func) {
//创建一个队列,并将其添加到队列的列表中
BlockingQueue<T> queue = new LinkedBlockingQueue<>();
queues.add(queue);
//创建一个 Spliterator,遍历队列中的元素
Spliterator<T> spliterator = new BlockingQueueSpliterator<>(queue);
//创建一个流,将 spliterator 做为数据源
Stream<T> source = StreamSupport.stream(spliterator, false);
//创建一个Future对象,已异步方式计算在流上执行特定函数的结果
return CompletableFuture.supplyAsync(() -> func.apply(source));
}
}
2.使用ForkingSteamConsumer实现Results接口
public Results getResults() {
ForkingStreamConsumer<T> consumer = builder();
try {
stream.sequential().forEach(consumer);
} finally {
consumer.finish();
}
return consumer;
}
3.完成ForkingStreamConsumer和BlockingQueueSpliterator,添加多个队列的元素
/**
* ForkingStreamConsumer
*
* @author houxiurong
* @date 2019-10-03
*/
public class ForkingStreamConsumer<T> implements Consumer<T>, Results {
static final Object END_OF_STREAM = new Object();
private final List<BlockingQueue<T>> queues;
private final Map<Object, Future<?>> actions;
public ForkingStreamConsumer(List<BlockingQueue<T>> queues,
Map<Object, Future<?>> actions) {
this.queues = queues;
this.actions = actions;
}
/**
* 将流中遍历的元素添加到所有的队列中
*
* @param t 流中元素
*/
@Override
public void accept(T t) {
queues.forEach(q -> q.add(t));
}
/**
* 将最后一个元素添加到队列中,表明该流已经结束
*/
void finish() {
accept((T) END_OF_STREAM);
}
/**
* 等待Future完成相关的计算,返回有特定键标识的处理结果
*
* @param key fork-key
* @param <R> 流处理结果
* @return
*/
@Override
public <R> R get(Object key) {
try {
return ((Future<R>) actions.get(key)).get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
遍历一个BolckingQueue并读取其中的元素的Spliterator
/**
* 遍历BlockingQueue并读取其中元素的 Spliterator
* 对流进行切分-流的延迟绑定
*
* @author houxiurong
* @date 2019-10-03
*/
public class BlockingQueueSpliterator<T> implements Spliterator<T> {
private final BlockingQueue<T> queue;
public BlockingQueueSpliterator(BlockingQueue<T> queue) {
this.queue = queue;
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
T take;
while (true) {
try {
take = queue.take();
break;
} catch (InterruptedException e) {
}
}
if (take != ForkingStreamConsumer.END_OF_STREAM) {
action.accept(take);
return true;
}
return false;
}
/**
* 对流进行切分
*
* @return
*/
@Override
public Spliterator<T> trySplit() {
return null;
}
/**
* 估计流大小
*
* @return
*/
@Override
public long estimateSize() {
return 0;
}
/**
* @return
*/
@Override
public int characteristics() {
return 0;
}
}
返回结果接口
/**
* 返回结果
*
* @author houxiurong
* @date 2019-10-03
*/
public interface Results {
/**
* 返回结果
*
* @param key fork-key
* @param <R> result
* @return
*/
<R> R get(Object key);
}
4.使用示例
对一个流进行fork操作,获取不同的执行结果放入Map中。
/**
* 流的fork处理
*
* @author houxiurong
* @date 2019-10-02
*/
public class ForkStreamDemo {
static List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
public static void main(String[] args) {
Stream<Dish> stream = menu.stream();
Results results = new StreamForker<>(stream)
.fork("shortMenu", s -> s.map(Dish::getName).collect(Collectors.joining(", ")))
.fork("totalCalories", s -> s.mapToInt(Dish::getCalories).sum())
.fork("mostCaloriesDish", s -> s.collect(Collectors.reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)).get())
.fork("dishesByType", s -> s.collect(Collectors.groupingBy(Dish::getType)))
.getResults();
String shortMenu = results.get("shortMenu");
int totalCalories = results.get("totalCalories");
Dish mostCaloriesDish = results.get("mostCaloriesDish");
Map<Dish.Type, List<Dish>> dishesByType = results.get("dishesByType");
System.out.println("Short Menu: " + shortMenu);
System.out.println("totalCalories: " + totalCalories);
System.out.println("mostCaloriesDish: " + JSON.toJSONString(mostCaloriesDish));
System.out.println("dishesByType: " + JSON.toJSONString(dishesByType));
}
}
/**
* Dish的定义
*
* @author houxiurong
* @date 2019-10-02
*/
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
public enum Type {
MEAT, FISH, OTHER
}
}
下面是输出结果:
Short Menu: pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon
totalCalories: 4200
mostCaloriesDish: {"calories":800,"name":"pork","type":"MEAT","vegetarian":false}
dishesByType: {"MEAT":[{"calories":800,"name":"pork","type":"MEAT","vegetarian":false},{"calories":700,"name":"beef","type":"MEAT","vegetarian":false},{"calories":400,"name":"chicken","type":"MEAT","vegetarian":false}],"OTHER":[{"calories":530,"name":"french fries","type":"OTHER","vegetarian":true},{"calories":350,"name":"rice","type":"OTHER","vegetarian":true},{"calories":120,"name":"season fruit","type":"OTHER","vegetarian":true},{"calories":550,"name":"pizza","type":"OTHER","vegetarian":true}],"FISH":[{"calories":300,"name":"prawns","type":"FISH","vegetarian":false},{"calories":450,"name":"salmon","type":"FISH","vegetarian":false}]}