我必须处理包含预定义记录布局的固定宽度文件,存在多种类型的记录,并且记录的第一个字符确定其类型。因为它是固定宽度,所以并不总是可以将整个记录类型放在一行上,因此第二个字符是记录的序列号。例如:
0This is the header record------------------------------------
1This is another record always existing out of one lin--------
21This is a record that can be composed out of multiple parts.
22This is the second part of record type 2--------------------
21This is a new record of type 2, first part.-----------------
22This is the second part of record type 2--------------------
23This is the third part of record type 2---------------------
...
使用Stream API,我想解析这个文件:
Stream<String> lines = Files.lines(Paths.get(args[1]));
lines.map(line -> RecordFactory.createRecord(line)).collect(Collectors.toList());
但是,由于此流逐行传递,因此在分析记录类型2的第一行(记录类型2序列1)时,记录2的映射不完整。应将下一行(记录类型2序列2)添加到先前映射的结果中。
如何使用lambda解决这个问题而不必破坏线程安全性?
答案 0 :(得分:3)
目前使用Stream API无法轻松实现对与谓词匹配的连续元素的操作。
一种选择是使用提供StreamEx操作的groupRuns
库:
返回由此流的元素列表组成的流,其中相邻元素根据提供的谓词进行分组。
以下代码将连续行的记录部件号严格大于上一行的记录部件号组合在一起。记录号用正则表达式提取,该表达式查找第一个被忽略的数字后的所有数字。
private static final Pattern PATTERN = Pattern.compile("\\d(\\d+)");
public static void main(String[] args) throws IOException {
try (StreamEx<String> stream = StreamEx.ofLines(Paths.get("..."))) {
List<Record> records =
stream.groupRuns((s1, s2) -> getRecordPart(s2) > getRecordPart(s1))
.map(RecordFactory::createRecord)
.toList();
}
}
private static final int getRecordPart(String str) {
Matcher matcher = PATTERN.matcher(str);
if (matcher.find()) {
return Integer.parseInt(matcher.group(1));
}
return 1; // if the pattern didn't find anything, it means the record is on a single line
}
这假定您的RecordFactory
会从Record
而不是List<String>
创建String
。请注意,此解决方案可以并行运行,但如果您希望获得更好的并行性能(以内存为代价),最好将文件内容存储到List
和后处理列表中。
答案 1 :(得分:0)
我认为您必须为Collector
界面制作自己的实现,例如Collector<String,List<String>,List<String>>
。
此收集器必须获取元素并将其添加到accumulator
中的临时列表第2个元素中,并且不将其添加到第3个元素,除非它已完成,如果您想要在其中运行它,则其实现将不是特别容易并行你必须实现combiner
,这将为你提供内存中的行列表,这样你的文件就会很大,这将是一个问题,其他的是使用有界队列生成管道,这不是直截了当的使用流可以检查https://github.com/jOOQ/jOOL。