Java 使用Streams按特定字符限制对文件中的行进行分组

Java 使用Streams按特定字符限制对文件中的行进行分组,java,file,java-8,java-stream,java.nio.file,Java,File,Java 8,Java Stream,Java.nio.file,我是Java 8 Stream API新手,在以下场景中使用时遇到问题: 我必须逐行读取文件,并将行分组,使其大小接近某个字符限制,然后将其发布到卡夫卡 public void publishStringToKafka(File outputFile) { try { Files.lines(outputFile.toPath()) .forEach(s -> kafkaProducer.publish

我是Java 8 Stream API新手,在以下场景中使用时遇到问题:

我必须逐行读取文件,并将行分组,使其大小接近某个字符限制,然后将其发布到卡夫卡

    public void publishStringToKafka(File outputFile) {
        try {
            Files.lines(outputFile.toPath())
                    .forEach(s -> kafkaProducer.publishMessageOnTopic(s, KAFKA_INGESTION_TOPIC));
        } catch (IOException e) {
            LOG.error("Could not read buffered file to send message on kafka.", e);
        } finally {
            try {
                Files.deleteIfExists(outputFile.toPath());
            } catch (IOException e) {
                LOG.error("Problem in deleting the buffered file {}.", outputFile.getName(), e);
            }
        }
    } 
现在,我完全可以使用传统的或声明式的方式来完成这项工作,即逐行读取文件,使用循环将它们组合起来,并在最接近1024个字符时,继续在kafka上发布消息。 但我想用流来做这个

注意:这段代码面临另一个问题,
Files.deleteIfExists(outputFile.toPath())命令在执行后不会删除文件,并且不会发生异常。而如果使用声明式样式,则文件将被成功删除


请提供帮助。

在这种情况下,
收集器.groupingBy()
将非常有用

Map<T, List<String>> result = Files.lines(outputFile.toPath())
  .collect(Collectors.groupingBy(Your::classifier, Collectors.toList()))

唯一棘手的部分是适当地实现分类器方法,但从问题中我了解到您知道如何做到这一点。

问题陈述您要做的是将流中的所有字符串按顺序组合到尽可能接近最大数量的字符,并创建一个新的字符列表。然后可以使用这个新创建的列表流式传输到卡夫卡。这不是一个容易解决的问题,因为你必须处理国家

解决方案

使用
采集器
累积值

 List<String> result = someStrings.stream()
                                  .collect(ArrayList::new, (list, string) -> accumulate(list, string), List::addAll);

如果您输入的列表[as,1234,213,bd,de]的最大字符数设置为5,它将返回所需的输出[as,1234,213bd,de]。

我为您的主要问题发布了一个解决方案。也许你可以将文件的问题转移到另一篇文章中去。deleteIfExists问题与主要问题无关。你没有关闭
流,因此如果删除源文件无效,你不应该感到惊讶。@Holger我应该这样做:
流=Files.lines(outputPath);stream.forEach(System.out::println);stream.close()否,您应该执行
try(Stream-Stream=Files.lines(outputPath)){Stream.forEach(System.out::println);‌ }。您甚至可以将
最终{Files.deleteIfExists(…);}
正文链接到它。好的,请使用参考资料进行尝试,很好的建议,谢谢:您的代码所做的,按属性对行进行排序,完全可以不进行分组步骤。您可以链接
.sorted(Comparator.comparating(your::classifier))
文件.line
的右侧,添加一个
forEachOrdered
就完成了。这是否是OP想要的,是另一回事。顺便说一句,您可以在这里使用。请注意,
Collector.of
允许只使用三个函数创建收集器,这比中的自定义实现要简单得多接口。您也可以在流上使用方便的three-arg
collect
方法。此外,累加器函数不必要地复杂,有两个操作相同的条件。逻辑应该是“如果不是空的,最后一个+新的足够小,请替换最后一个,否则只需添加新的”。此外,
remove(lastIndex);add(newValue);
可以替换为
set(lastIndex,newValue);
@Holger更新了答案。我应该删除长版本吗?或者它是否包含对读者有价值的信息?请注意,
(列表,字符串)->{accumulate(列表,字符串);}
可以简化为
(列表,字符串)->accumulate(列表,字符串)
甚至是方法引用。是否保留长版本是您的决定,但由于它缺少合并器,这在如此大的代码中并不明显,我不会保留它。顺便说一下,当您使用三个参数版本的
Stream.collect(…)
,您可以使用
列表::addAll
作为组合器。这并不完全正确(反正没有正确的解决方案),但至少您不会因此而丢失字符串。@Furquan Ahmed:start。我将其更改为
流。collect(…)
方法,因为我喜欢
列表::addAll
的想法。总是想知道如果我将参数放在自己的行上,代码是否看起来更干净。我更喜欢使用内联lambda表示法进行累加,出于某些原因,我不喜欢在不在“框架”中的代码中返回值,如
BiConsumer
 List<String> result = someStrings.stream()
                                  .collect(ArrayList::new, (list, string) -> accumulate(list, string), List::addAll);
 private void accumulate(ArrayList<String> list, String string) {
        if (list.isEmpty() || list.get(list.size() -1).length() + string.length() > MAXIMUM_CHARACTERS){
            list.add(string);
        } else {
            list.set(list.size()-1, list.get(list.size()-1) + string);
        }
    }