我的确切方案是批量插入数据库,所以我想累积DOM对象然后每1000个,刷新它们。
我通过将代码放入累加器来检测丰满度然后刷新来实现它,但这似乎是错误的 - 刷新控件应该来自调用者。
我可以将流转换为List然后以迭代方式使用subList,但这看起来也很笨重。
有一个简洁的方法来处理每n个元素然后继续流,而只处理流一次?
答案 0 :(得分:8)
优雅是旁观者的眼睛。如果您不介意在groupingBy
中使用有状态函数,则可以执行以下操作:
AtomicInteger counter = new AtomicInteger();
stream.collect(groupingBy(x->counter.getAndIncrement()/chunkSize))
.values()
.forEach(database::flushChunk);
这不会赢得原始解决方案的任何性能或内存使用点,因为它在执行任何操作之前仍会实现整个流。
如果您想避免实现列表,流API将无法帮助您。你必须得到流的迭代器或分裂器,并做这样的事情:
Spliterator<Integer> split = stream.spliterator();
int chunkSize = 1000;
while(true) {
List<Integer> chunk = new ArrayList<>(size);
for (int i = 0; i < chunkSize && split.tryAdvance(chunk::add); i++){};
if (chunk.isEmpty()) break;
database.flushChunk(chunk);
}
答案 1 :(得分:5)
使用库StreamEx解决方案看起来像
Stream<Integer> stream = IntStream.iterate(0, i -> i + 1).boxed().limit(15);
AtomicInteger counter = new AtomicInteger(0);
int chunkSize = 4;
StreamEx.of(stream)
.groupRuns((prev, next) -> counter.incrementAndGet() % chunkSize != 0)
.forEach(chunk -> System.out.println(chunk));
输出:
[0, 1, 2, 3]
[4, 5, 6, 7]
[8, 9, 10, 11]
[12, 13, 14]
groupRuns
接受谓词,决定2个元素是否应该在同一个组中。
一旦找到第一个不属于它的元素,就会生成一个组。
答案 2 :(得分:4)
如果你的项目有guava依赖,你可以这样做:
StreamSupport.stream(Iterables.partition(simpleList, 1000).spliterator(), false).forEach(...);
答案 3 :(得分:3)
您可以通过<创建项目 块大小的块(List<T>
) / p>
代码:
public static <T> Stream<List<T>> chunked(Stream<T> stream, int chunkSize) {
AtomicInteger index = new AtomicInteger(0);
return stream.collect(Collectors.groupingBy(x -> index.getAndIncrement() / chunkSize))
.entrySet().stream()
.sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue);
}
使用示例:
Stream<Integer> stream = IntStream.range(0, 100).mapToObj(Integer::valueOf);
Stream<List<Integer>> chunked = chunked(stream, 8);
答案 4 :(得分:1)
正如Misha正确地说的那样,优雅是旁观者的眼睛。我个人认为一个优雅的解决方案是让插入数据库的类执行此任务。与BufferedWriter
类似。这样,它不依赖于您的原始数据结构,甚至可以在多个流之后使用。我不确定这是否正是您在累加器中使用您认为错误的代码的意思。我不认为这是错误的,因为像BufferedWriter
这样的现有类是这样的。你可以通过这种方式在调用者处获得一些刷新控制,只需在编写器上调用flush()
。
类似下面的代码。
class BufferedDatabaseWriter implements Flushable {
List<DomObject> buffer = new LinkedList<DomObject>();
public void write(DomObject o) {
buffer.add(o);
if(buffer.length > 1000)
flush();
}
public void flush() {
//write buffer to database and clear it
}
}
现在您的流处理如下:
BufferedDatabaseWriter writer = new BufferedDatabaseWriter();
stream.forEach(o -> writer.write(o));
//if you have more streams stream2.forEach(o -> writer.write(o));
writer.flush();
如果要使用多线程,可以运行flush异步。从流中获取不能同时进行,但我认为无论如何都有办法从流并行计算1000个元素。
您还可以扩展编写器以允许在构造函数中设置缓冲区大小,或者您可以使其实现AutoCloseable
并使用ressources等尝试运行它。来自BufferedWriter
的好东西。
答案 5 :(得分:1)
看起来像不,导致创建块意味着减少流,而减少意味着终止。如果您需要保持流的性质并在不收集所有数据的情况下处理数据块,请在此处给出我的代码(不适用于并行流):
private static <T> BinaryOperator<List<T>> processChunks(Consumer<List<T>> consumer, int chunkSize) {
return (data, element) -> {
if (data.size() < chunkSize) {
data.addAll(element);
return data;
} else {
consumer.accept(data);
return element; // in fact it's new data list
}
};
}
private static <T> Function<T, List<T>> createList(int chunkSize) {
AtomicInteger limiter = new AtomicInteger(0);
return element -> {
limiter.incrementAndGet();
if (limiter.get() == 1) {
ArrayList<T> list = new ArrayList<>(chunkSize);
list.add(element);
return list;
} else if (limiter.get() == chunkSize) {
limiter.set(0);
}
return Collections.singletonList(element);
};
}
以及使用方法
Consumer<List<Integer>> chunkProcessor = (list) -> list.forEach(System.out::println);
int chunkSize = 3;
Stream.generate(StrTokenizer::getInt).limit(13)
.map(createList(chunkSize))
.reduce(processChunks(chunkProcessor, chunkSize))
.ifPresent(chunkProcessor);
static Integer i = 0;
static Integer getInt()
{
System.out.println("next");
return i++;
}
它将打印
下一个 下一个 下一个 下一个 0 1个 2 下一个 下一个 下一个 3 4 5 下一个 下一个 下一个 6 7 8 下一个 下一个 下一个 9 10 11 12
背后的想法是使用“模式”在地图操作中创建列表
[1 ,,],[2],[3],[4 ,, ......
并使用reduce进行合并(+处理)。
[1,2,3],[4,5,6],...
并且不要忘记使用
处理最后一个“修剪过的”块.ifPresent(chunkProcessor);
答案 6 :(得分:0)
上面的大多数答案都没有利用流的好处,例如节省内存。您可以尝试使用迭代器解决问题
Stream<List<T>> chunk(Stream<T> stream, int size) {
Iterator<T> iterator = stream.iterator();
Iterator<List<T>> listIterator = new Iterator<>() {
public boolean hasNext() {
return iterator.hasNext();
}
public List<T> next() {
List<T> result = new ArrayList<>(size);
for (int i = 0; i < size && iterator.hasNext(); i++) {
result.add(iterator.next());
}
return result;
}
};
return StreamSupport.stream(((Iterable<List<T>>) () -> listIterator).spliterator(), false);
}
答案 7 :(得分:0)
你可以使用这个类,https://github.com/1wpro2/jdk-patch/blob/main/FixedSizeSpliterator.java。
传入块大小作为 THRESHOLD
new FixedSizeSpliterator(T[] values, int threshold)