使用Java加载和处理非常大的文件

时间:2018-10-29 10:45:37

标签: java file

我正在尝试加载包含大量行(> 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();
    }       
}

4 个答案:

答案 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)

我认为这里缺少一些关键概念:

  1. 您说的文件大小为2GB。这并不意味着当您将文件数据加载到ArrayList中时,内存中的大小也将为2GB。为什么?通常,文件使用UTF-8字符编码存储数据,而JVM使用UTF-16内部存储String值。因此,假设您的文件仅包含ASCII字符,则每个字符在文件系统中占据1个字节,而在内存中占据2个字节。假设(为简单起见)所有String值都是唯一的,则将需要存储String引用的空间,每个引用均为32位(假设使用压缩oop的64位系统)。您的堆是多少(不包括其他内存区域)?您的伊甸园空间和旧空间是多少?我很快会再次谈到这一点。

  2. 在您的代码中,您没有指定ArrayList的大小。在这种情况下,这是一个错误。为什么? JVM创建一个小的ArrayList。一段时间后,JVM看到这个家伙一直在输入数据。让我们创建一个更大的ArrayList并将旧ArrayList的数据复制到新列表中。当您处理如此庞大的数据量时,此事件具有更深的含义:首先,请注意旧阵列和新阵列(具有数百万个条目)同时在内存中占用空间,其次,不必要地将数据从一个阵列复制到另一个阵列-每当阵列空间用完时,不是一次或两次,而是重复一次。旧阵列会发生什么?好吧,它已被丢弃,需要进行垃圾收集。因此,这些重复的数组复制和垃圾回收会减慢该过程。 CPU在这里真的很努力。当您的数据不再适合年轻一代(小于堆)时,会发生什么?也许您需要使用JVisualVM之类的工具来查看行为。

总而言之,我的意思是,有很多原因会导致2GB文件填满更大的堆以及导致处理性能差的原因。