我正在尝试加载包含大量行(> 500万行)的csv文件,但是当尝试将它们全部处理为每个值的数组列表时,它会大大减慢速度
我尝试了几种读取和从文件加载的输入列表中删除的变体,但是即使我为该进程分配了14GB的内存,但文件只有2GB的内存,它仍然耗尽堆空间
我知道我需要删除值,这样我才不会在内存中出现重复的引用,因此我不会以行的arraylist和各个逗号分隔值的arraylist结尾,但是我不知道该怎么做
编辑:作为参考,在这种特殊情况下,数据最终应包含16 * 5百万个值。
如果有更好的解决方案,我全力以赴
加载此文件的目的是使用适当的方法(如select和select where)将其作为数据库处理,所有方法均由工作表类处理。在我的较小的36,000行示例文件中,它的效果很好,但是我想它的缩放效果不是很好
当前代码:
//Load method to load it from file
private static CSV loadCSV(String filename, boolean absolute)
{
String fullname = "";
if (!absolute)
{
fullname = baseDirectory + filename;
if (!Load.exists(fullname,false))
return null;
}
else if (absolute)
{
fullname = filename;
if (!Load.exists(fullname,false))
return null;
}
ArrayList<String> output = new ArrayList<String>();
AtomicInteger atomicInteger = new AtomicInteger(0);
try (Stream<String> stream = Files.lines(Paths.get(fullname)))
{
stream.forEach(t -> {
output.add(t);
atomicInteger.getAndIncrement();
if (atomicInteger.get() % 10000 == 0)
{
Log.log("Lines done " + output.size());
}
});
CSV c = new CSV(output);
return c;
}
catch (IOException e)
{
Log.log("Error reading file " + fullname,3,"FileIO");
e.printStackTrace();
}
return null;
}
//Process method inside CSV class
public CSV(List<String> output)
{
Log.log("Inside csv " + output.size());
ListIterator<String> iterator = output.listIterator();
while (iterator.hasNext())
{
ArrayList<String> d = new ArrayList<String>(Arrays.asList(iterator.next().split(splitter,-1)));
data.add(d);
iterator.remove();
}
}
答案 0 :(得分:3)
您需要使用任何提供任务(选择,分组)所需功能的数据库。 任何数据库都可以有效读取和聚合500万行。 不要尝试使用“对ArrayList进行操作”,它仅在小型数据集上有效。
答案 1 :(得分:0)
尝试使用纯Java解决此问题非常困难。我建议使用像Apache Spark这样的处理引擎,该引擎可以通过提高并行度来以分布式方式处理文件。 Apache Spark具有特定的API来加载CSV文件:
spark.read.format("csv").option("header", "true").load("../Downloads/*.csv")
您可以将其转换为RDD或Dataframe并对其执行操作。 您可以在网上找到更多信息,或here
答案 2 :(得分:0)
我将拥有一种方法,该方法将从文件中读取的一行作为参数并将其拆分为字符串列表,然后返回该列表。然后,我将该列表添加到文件读取循环中的CSV对象中。这意味着只有一个大型集合,而不是两个,并且读取行可以更快地从内存中释放出来。 像这样
CSV csv = new CSV();
try (Stream<String> stream = Files.lines(Paths.get(fullname))) {
stream.forEach(t -> {
List<String> splittedString = splitFileRow(t);
csv.add(splittedString);
});
答案 3 :(得分:0)
我认为这里缺少一些关键概念:
您说的文件大小为2GB。这并不意味着当您将文件数据加载到ArrayList
中时,内存中的大小也将为2GB。为什么?通常,文件使用UTF-8字符编码存储数据,而JVM使用UTF-16内部存储String
值。因此,假设您的文件仅包含ASCII字符,则每个字符在文件系统中占据1个字节,而在内存中占据2个字节。假设(为简单起见)所有String
值都是唯一的,则将需要存储String
引用的空间,每个引用均为32位(假设使用压缩oop的64位系统)。您的堆是多少(不包括其他内存区域)?您的伊甸园空间和旧空间是多少?我很快会再次谈到这一点。
在您的代码中,您没有指定ArrayList
的大小。在这种情况下,这是一个错误。为什么? JVM创建一个小的ArrayList
。一段时间后,JVM看到这个家伙一直在输入数据。让我们创建一个更大的ArrayList
并将旧ArrayList
的数据复制到新列表中。当您处理如此庞大的数据量时,此事件具有更深的含义:首先,请注意旧阵列和新阵列(具有数百万个条目)同时在内存中占用空间,其次,不必要地将数据从一个阵列复制到另一个阵列-每当阵列空间用完时,不是一次或两次,而是重复一次。旧阵列会发生什么?好吧,它已被丢弃,需要进行垃圾收集。因此,这些重复的数组复制和垃圾回收会减慢该过程。 CPU在这里真的很努力。当您的数据不再适合年轻一代(小于堆)时,会发生什么?也许您需要使用JVisualVM之类的工具来查看行为。
总而言之,我的意思是,有很多原因会导致2GB文件填满更大的堆以及导致处理性能差的原因。