我正在测试以找出读取和处理csv文件的最佳方法。 所以我需要阅读csv文件的每一行并分析每一行。 所以基本上所有工作都适用于包含数千行的文件。但是,当尝试使用包含超过1百万行的CSV文件时,我会遇到内存不足异常。我认为Stream Parallel会表现得更快。所以我有点困惑为什么我得到这个内存不足错误。 Java如何处理并行读取?
下面是顺序和并行的测试代码读取文件。
String filename = "c:\\devs\\files\\datas.csv"; // 193MB
Path path = Paths.get(filename);
@Test
public void testFileExist() {
assertTrue(Files.exists(path));
}
@Test
public void testSingleThreadRead() {
Function<Path, String> processfile = (Path p) -> {
String result = "";
try {
result = Files.lines(p).collect(Collectors.joining(" ,"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
};
long start = System.currentTimeMillis();
String result = processfile.apply(path);
long end = System.currentTimeMillis();
assertFalse(result.isEmpty());
System.out.println(end -start + "ms");
}
@Test
public void testSingleThreadReadParallel() {
Function<Path, String> processfile = (Path p) -> {
String result = "";
try {
result = Files.lines(p).parallel().collect(Collectors.joining(" ,"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
};
long start = System.currentTimeMillis();
String result = processfile.apply(path);
long end = System.currentTimeMillis();
assertFalse(result.isEmpty());
System.out.println(end -start + "ms");
}
异常
java.lang.OutOfMemoryError
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at java.util.concurrent.ForkJoinTask.getThrowableException(Unknown Source)
at java.util.concurrent.ForkJoinTask.reportException(Unknown Source)
at java.util.concurrent.ForkJoinTask.invoke(Unknown Source)
at java.util.stream.ReduceOps$ReduceOp.evaluateParallel(Unknown Source)
at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
at java.util.stream.ReferencePipeline.collect(Unknown Source)
at test.TestProcessFile.lambda$1(TestProcessFile.java:48)
at test.TestProcessFile.testSingleThreadReadParallel(TestProcessFile.java:58)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at
更新
在separe类中运行并行处理但仍然遇到此异常
Exception in thread "main" java.lang.OutOfMemoryError
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at java.util.concurrent.ForkJoinTask.getThrowableException(Unknown Source)
at java.util.concurrent.ForkJoinTask.reportException(Unknown Source)
at java.util.concurrent.ForkJoinTask.invoke(Unknown Source)
at java.util.stream.ReduceOps$ReduceOp.evaluateParallel(Unknown Source)
at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
at java.util.stream.ReferencePipeline.collect(Unknown Source)
at ProcessFileParallel.lambda$0(ProcessFileParallel.java:19)
at ProcessFileParallel.main(ProcessFileParallel.java:29)
Caused by: java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(Unknown Source)
at java.lang.AbstractStringBuilder.append(Unknown Source)
at java.lang.StringBuilder.append(Unknown Source)
at java.util.StringJoiner.merge(Unknown Source)
at java.util.stream.Collectors$$Lambda$5/990368553.apply(Unknown Source)
at java.util.stream.ReduceOps$3ReducingSink.combine(Unknown Source)
at java.util.stream.ReduceOps$3ReducingSink.combine(Unknown Source)
at java.util.stream.ReduceOps$ReduceTask.onCompletion(Unknown Source)
at java.util.concurrent.CountedCompleter.tryComplete(Unknown Source)
at java.util.stream.AbstractTask.compute(Unknown Source)
at java.util.concurrent.CountedCompleter.exec(Unknown Source)
at java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(Unknown Source)
at java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
at java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)
答案 0 :(得分:5)
您的代码在testSingleThreadReadParallel
失败,而不是并行。问题出在其他地方 - 可能将整个文件收集为String。
Files.lines
被缓冲(查看实现),因此读取文件很可能不会导致任何问题。
但是将该文件收集到单个String
显然需要大量内存,远远超过文件大小本身。
实际上,根据我的理解,并行读取这些文件需要更多的内存而不是顺序。每个线程将并行读取它在内存中的 ,因此您的并行方法将需要更多内存。更多我的意思是来自Stream.lines
的CPU * BufferSize数量。
<强> EDIT2 强>
花了一些时间后,我意识到你的问题必须在其他地方。就像你的文件中有实际行吗?或者你可能处于极限 - 我的意思是并行会增加内存,但不会增加 。您可能需要稍微增加-Xms
和-Xmx
。
例如,我为测试目的创建了一个包含247MB
虚拟数据的文件,并在其上运行此代码:
Path p = Paths.get("/private/tmp/myfile.txt");
Stream<String> s = Files.lines(p).parallel(); // and without parallel
s.forEach(System.out::println);
我对-Xmx200m -Xms200m
和parallel
处理使用的设置均为sequential
。这小于实际文件大小。它仍然很好。
您的主要问题是您正在将所有内容收集到单个字符串中,从而使其大小非常大。在jdk-8下我的机器上收集所有字符串需要至少 1.5GB的堆。
同样非常好的阅读here
答案 1 :(得分:0)
尝试更改JVM args中的JVM内存设置,尤其是-Xmx(最小堆内存)arg。见Oracle's Documentation.
另一个(甚至更好)选项是按照评论中的建议以块的形式读取您的文件。这将确保用于读取文件的最大内存大小。