我们说我有一个巨大的文件,我想读每行100个并做一个操作。 (我想结合100行并发送休息请求)
在Java 7中,我会做类似下面的事情。
try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
String line;
int count = 0;
List<String> list = new ArrayList<>();
while ((line = br.readLine()) != null) {
list.add(line);
count++;
if (count % 100 == 0) {
//do the operation on list
list = new ArrayList();
}
}
} catch (IOException e) {
e.printStackTrace();
}
我们可以在这里利用Java 8 Stream吗? 我知道我们可以做这样的事情,但它在每一行而不是100行上运行。所以我认为foreach不是这里的选择。
try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
stream.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
答案 0 :(得分:3)
如果你不喜欢上面的方法,你可以简单地使用第二种方法,但是你不能并行创建部分流,因为你必须按顺序阅读lines
。例如:
split(Paths.get("file"), 100).forEach(this::sendRequest);
void sendRequest(List<String> each) {
// then you must send the rest request in parallel here
}
Stream<List<String>> split(Path path, int limit) throws IOException {
// skip the remaining lines if its size < limit
return split(Files.lines(path), limit, true);
}
<T> Stream<List<T>> split(Stream<T> source,
int limit, boolean skipRemainingElements) {
//variables just for printing purpose
Spliterator<T> it = source.spliterator();
long size = it.estimateSize();
int c = it.characteristics();// characteristics
return stream(new AbstractSpliterator<List<T>>(size, c) {
private int thresholds = skipRemainingElements ? limit : 1;
@Override
@SuppressWarnings("StatementWithEmptyBody")
public boolean tryAdvance(Consumer<? super List<T>> action) {
List<T> each = new ArrayList<>(limit);
while (each.size() < limit && it.tryAdvance(each::add)) ;
if (each.size() < thresholds) return false;
action.accept(each);
return true;
}
}, false).onClose(source::close);
}
答案 1 :(得分:0)
您可以使用Stream#skip和Stream#limit拆分流,然后每100行并行发送一个休息请求。例如:
split(Paths.get("file"), 100).parallel().forEach(this::sendRequest);
Stream<Stream<String>> split(Path path, int limit) throws IOException {
return LongStream.of(0, lines(path) / limit).parallel()
.map(it -> it * limit)
.mapToObj(offset -> {
try {
return Files.lines(path).skip(offset).limit(limit);
} catch (IOException e) {
throw new RejectedExecutionException(e);
}
});
}
long lines(Path path) throws IOException {
try (LineNumberReader in = open(path)) {
return in.getLineNumber();
}
}
LineNumberReader open(Path path) throws IOException {
return new LineNumberReader(newBufferedReader(path));
}
void sendRequest(Stream<String> each) {
try (BufferedWriter out = null) {// todo: create the output writer
each.forEach(line -> {
try {
out.write(line);
} catch (IOException e) {
// todo: handle error
}
});
} catch (IOException ex) {
//todo: handle error
}
}
IF 您需要更高的性能,您必须在split
&amp;中实现自己的算法。 lines
方法。注意LineNumberReader#getLineNumber
是int
而不是long
。对于计算行,我认为有许多开源项目可以并行计算总行数。
AND 这只是一个骨架,如果你想要最高的性能。首先,您需要将行信息索引,例如:(totalLines
和offset
)并行(算法,如合并排序)到内存或磁盘(如果需要)。然后您可以使用RandomeAccessFile
快速跳转到offset
。
total_lines|ofsset1|offset2|...|offsetN
注意:行信息文件中没有分隔符|
。并且您必须使用DataOutputStream#writeLong
作为long
写入每个值,因为以此格式写入行信息文件,您可以按字节计算offsetN
的位置,例如:{{1}然后,您可以通过从8*M; M=(1..N)
读取8个字节来获取offsetN
。
实际上,应该创建索引文件时,如果创建了巨大的文件(,如果文件太大,需要拆分),那么它可以节省您的使用时间。进一步