[英]Read X lines at a time from a text file using Java Streams?
我有一个“普通的旧文本文件”,其中行以新行字符结尾。 出于任意原因,我需要一次读取和解析此文本文件4(X为通用)行。
我想将Java流用于此任务,我知道我可以将文件转换为如下所示的流:
try (Stream<String> stream = Files.lines(Paths.get("file.txt""))) {
stream.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
但是,我如何使用Java的Stream API将文件“捆绑”成4个连续的行?
这是java.util.Scanner
的工作。 在Java 9中,您可以简单地使用
try(Scanner s = new Scanner(PATH)) {
s.findAll("(.*\\R){1,4}")
.map(mr -> Arrays.asList(mr.group().split("\\R")))
.forEach(System.out::println);
}
对于Java 8,您可以使用此答案的findAll
的后端口。 为该方法添加import static
后,您可以像使用它一样使用它
try(Scanner s = new Scanner(PATH)) {
findAll(s, Pattern.compile("(.*\\R){1,4}"))
.map(mr -> Arrays.asList(mr.group().split("\\R")))
.forEach(System.out::println);
}
请注意,匹配操作的结果是包含最多四行的单个字符串(最后一行较少)。 如果这适合您的后续操作,您可以跳过将该字符串拆分为单独的行。
您甚至可以使用MatchResult
的属性来更复杂地处理块,例如
try(Scanner s = new Scanner(PATH)) {
findAll(s, Pattern.compile("(.*)\\R(?:(.*)\\R)?(?:(.*)\\R)?(?:(.*)\\R)?"))
.flatMap(mr -> IntStream.rangeClosed(1, 4)
.mapToObj(ix -> mr.group(ix)==null? null: ix+": "+mr.group(ix)))
.filter(Objects::nonNull)
.forEach(System.out::println);
}
有一种方法可以使用标准Java 8 Stream API将文件内容分区并处理为n
-size块。 您可以使用Collectors.groupingBy()
将文件内容分区为块 - 您可以将它们收集为Collection<List<String>>
,也可以在收集所有行时应用一些处理(例如,您可以将它们连接到单个String) 。
看一下下面的例子:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class ReadFileWithStream {
public static void main(String[] args) throws IOException {
// Path to a file to read
final Path path = Paths.get(ReadFileWithStream.class.getResource("/input.txt").toURI());
final AtomicInteger counter = new AtomicInteger(0);
// Size of a chunk
final int size = 4;
final Collection<List<String>> partitioned = Files.lines(path)
.collect(Collectors.groupingBy(it -> counter.getAndIncrement() / size))
.values();
partitioned.forEach(System.out::println);
}
}
我的输入文件包含一些数字(一行中有一个数字) ,当我运行以下代码时,我会得到类似的结果:
[0, 0, 0, 2]
[0, -3, 2, 0]
[1, -3, -8, 0]
[2, -12, -11, -11]
[-8, -1, -8, 0]
[2, -1, 2, -1]
... and so on
Collectors.groupingBy()
允许我使用不同的下游收集器。 默认使用Collectors.toList()
,因此我的结果被累积到List<String>
,我得到Collection<List<String>>
作为最终结果。
假设我想要读取4个大小的块,我想将所有数字加在一个块中。 在这种情况下,我将使用Collectors.summingInt()
作为我的下游函数,返回的结果是Collection<Integer>
:
final Collection<Integer> partitioned = Files.lines(path)
.collect(Collectors.groupingBy(it -> counter.getAndIncrement() / size, Collectors.summingInt(Integer::valueOf)))
.values();
输出:
2
-1
-10
-32
-17
2
-11
-49
... and so on
最后但并非最不重要。 Collectors.groupingBy()
返回一个映射,其中值按特定键分组。 这就是为什么最后我们调用Map.values()
来获取此映射中包含的值的集合。
希望能帮助到你。
这是使用Guava的Iterators.partition
方法的简单方法:
try (Stream<String> stream = Files.lines(Paths.get("file.txt""))) {
Iterator<List<String>> iterator = Iterators.partition(stream.iterator(), 4);
// iterator.next() returns each chunk as a List<String>
} catch (IOException e) {
// handle exception properly
}
这仅适用于顺序处理,但如果您从磁盘读取文件,我很难想象并行处理会带来什么好处......
编辑:如果你想,而不是使用迭代器,你可以再次将其转换为流:
Stream<List<String>> targetStream = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED),
false);
如果你想坚持使用流,我看到的唯一解决方案是编写自己的自定义收集器。 它并非用于此目的,但您可以使用它。
private static final class CustomCollector {
private List<String> list = new ArrayList<>();
private List<String> acumulateList = new ArrayList<>();
public void accept(String str) {
acumulateList.add(str);
if (acumulateList.size() == 4) { // acumulate 4 strings
String collect = String.join("", acumulateList);
// I just joined them in on string, you can do whatever you want
list.add(collect);
acumulateList = new ArrayList<>();
}
}
public CustomCollector combine(CustomCollector other) {
throw new UnsupportedOperationException("Parallel Stream not supported");
}
public List<String> finish() {
if(!acumulateList.isEmpty()) {
list.add(String.join("", acumulateList));
}
return list;
}
public static Collector<String, ?, List<String>> collector() {
return Collector.of(CustomCollector::new, CustomCollector::accept, CustomCollector::combine, CustomCollector::finish);
}
}
并像这样使用它:
stream.collect(CustomCollector.collector());
如果您愿意使用RxJava ,则可以使用其buffer
功能:
Stream<String> stream = Files.lines(Paths.get("file.txt"))
Observable.fromIterable(stream::iterator)
.buffer(4) // Observable<List<String>>
.map(x -> String.join(", ", x)) // Observable<String>
.forEach(System.out::println);
buffer
创建一个Observable
,用于收集特定大小的列表中的元素。 在上面的例子中,我通过map
添加了另一个转换,使列表更加友好,但您可以根据需要转换Observable
。 例如,如果您有一个方法processChunk
,它将List<String>
作为参数并返回一个String
,您可以执行以下操作:
Observable<String> fileObs =
Observable.fromIterable(stream::iterator)
.buffer(4)
.map(x -> processChunk(x));
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.