我有一个来自使用Spring data Jpa的数据库的数据流,该数据流需要Json序列化并写入Http响应,而不存储在内存中。这是示例代码。
try (Stream<Employee> dataStream = empRepo.findAllStream()) {
response.setHeader("content-type", "application/json");
PrintWriter respWriter = response.getWriter();
respWriter.write("["); // array begin
dataStream.forEach(data -> {
try {
respWriter.write(jsonSerialize(data));
respWriter.write(",");
} catch (JsonProcessingException e) {
log(e);
}
entityManager.detach(data);
});
respWriter.write("]"); // array end
respWriter.flush();
} catch (IOException e) {
log(e);
}
}
但是这个逻辑会在最后一个元素后面写一个额外的逗号。如果respWriter.write(",");
是最后一个元素,我怎么能不做呢?
有一些流运算符的解决方案-peek
、reduce
等,但最优化的解决方案是什么?是否有类似Stream.hasNext()
的东西,以便我可以在forEach
中使用if
条件?
首先,我想说的是,我认为您的问题不适合单个管道流。您使用write
调用和detach
调用都会产生副作用。也许你更擅长正常的for循环?还是使用多个流?
也就是说,你可以使用Eran在回答这个问题时描述的技术:用分离器交错流中的元素
try (Stream<Employee> dataStream = empRepo.findAllStream()) {
response.setHeader("content-type", "application/json");
PrintWriter respWriter = response.getWriter();
respWriter.write("["); // array begin
dataStream.map(data -> {
try {
String json = jsonSerialize(data);
// NOTE! It is confusing to have side effects like this in a stream!
entityManager.detach(data);
return json;
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
})
.flatMap(json -> Stream.of(",", json))
.skip(1)
.forEach(respWriter::write);
respWriter.write("]"); // array end
respWriter.flush();
} catch (IOException e) {
log(e);
}
对于这个特定场景,您可以使用Collectors.joining
printWriter.write(dataStream
.map(this::deserializeJson)
.peek(entityManager::detach)
.collect(Collectors.joining(","));
然而,由于您正在执行流中不鼓励的副作用,并且您特别询问了hasNext()
操作,并且由于此流解决方案将在内存中构建一个大字符串,因此您可能更喜欢将流转换为迭代器并使用命令式循环:
Iterator<Employee> it = dataStream.iterator();
while(it.hasNext()) {
Employee data = it.next();
...
// skip writing delimiter on last entry
if (it.hasNext()){
respWriter.write(",")
}
}