【问题标题】:Calculate statistics on +20millions records in Java在 Java 中计算 +2000 万条记录的统计信息
【发布时间】:2018-08-13 15:52:51
【问题描述】:

我有 csv 文件 (600 MB) 和 2000 万行。 我需要读取所有这些数据,从中创建 java 对象列表,并计算对象字段的一些指标,例如平均值、中位数、最大值、总和和其他统计信息。在 Java 中最好的方法是什么? 我尝试了简单的 .forEach 循环,它需要一段时间(20 分钟)来迭代它。

更新: 我使用 BufferReader 读取数据并将 csv 文件转换为一些 Java 类的对象列表。它非常快。 它在 forEach 循环中停留了 20 分钟,我尝试遍历这 2000 万个对象列表并将它们分成 3 个列表,具体取决于当前对象中的值。 所以基本上,我遍历整个列表一次,我有 if/else 条件,我检查对象中的某些字段是否等于“X”,“Y”或“Z”,并根据答案,分开将这 2000 万条记录分成 3 个列表。

然后,对于这 3 个列表,我需要计算不同的统计数据:例如中位数、平均值、总和等

【问题讨论】:

  • 可以拆分文件,使用多线程
  • 如果你不愿意展示你迄今为止所尝试的东西,那就没什么好说的了。
  • 或者你应该尝试spring-batch,根据你的要求。
  • 您所描述的一切都不应超过 30 秒。我们需要更多信息。

标签: java csv stream bigdata


【解决方案1】:

听起来您的程序只是在向列表中添加内容时耗尽了内存。如果您接近分配给 JVM 的内存限制,垃圾收集器将花费大部分时间来尽其所能防止您耗尽内存。

您应该使用fast CSV 库(例如univocity-parsers)来遍历每一行并执行您需要的计算,而无需将所有内容存储在内存中。像这样使用它:

CsvParserSettings parserSettings = new CsvParserSettings(); //configure the parser
parserSettings.selectFields("column3", "column1", "column10"); //only read values from columns you need

CsvParser parser = new CsvParser(parserSettings);

//use this if you just need plain strings
for(String[] row : parser.iterate(new File("/path/to/your.csv"))){
    //do stuff with the row
}

//or use records to get values ready for calculation
for(Record record : parser.iterateRecords(new File("/path/to/your.csv"))){
    int someValue = record.getInt("columnName");
    //perform calculations
}

如果由于某种原因您需要多次遍历所有行,只需将数据存储在一个巨大的列表中。在这种情况下,使用-Xms8G -Xmx8G 为您的程序分配更多内存。请记住,您不能拥有大小超过 Integer.MAX_VALUEArrayList,因此即使您有足够的内存,这也是您的下一个限制。

如果你真的需要一个列表,你可以像这样使用解析器:

List<Record> twentyMillionRecords = parser.parseAllRecords(new File("/path/to/your.csv"), 20_000_000);

否则,您最好的选择是根据需要多次运行解析器。我建议的解析器每次都应该花几秒钟的时间来遍历文件。

希望对你有帮助

免责声明:我是这个库的作者。它是开源和免费的(apache 2.0 许可证)

【讨论】:

  • 我支持 GC 减慢速度的观点。尝试增加 JVM 内存分配。
【解决方案2】:

在广泛处理超过 600Mb 的数据量后,我可以发表两条声明:

  • 600Mb 不是大量数据,尤其是当我们谈论表格数据时;
  • 这些数量与 BigData 无关,实际上可以在内存中的传统硬件上轻松处理,这是最快的选择。

但是,您应该做的是确保将该数据读入按列连续的数组中,并使用直接对这些按列数据的连续数组进行操作的方法。

因为它是按行存储的 csv 文件,所以最好将其整块读取到字节数组中,然后将其解析为按列预分配的表示形式。

将 600Mb 的块读入 SSD 上的内存应该需要几秒钟,解析它取决于您的算法(但必须能够立即在该结构中查找)。内存方面,您将使用大约 600Mb 的三倍,但使用 16Gb 的机器应该很容易。

因此,不要急于使用 SQL 或切片文件,也不要将每个单元格实例化为 Java 对象。也就是说,在这种例外情况中,您不需要Java 对象列表,您需要double[] 等。您可以使用ArrayLists,但如果您预先分配确切的大小。其他标准集合会杀了你。

说了这么多,我宁愿推荐pythonnumpy 来完成这项任务,而不是Java。 Java 擅长处理对象,而不擅长处理连续的内存块和相应的操作。 C++ 也可以,甚至 R

【讨论】:

    【解决方案3】:

    我强烈建议不要将所有 600MB 加载到 RAM 中并将其用作 Java 对象。 正如你所说,这需要很长时间才能加载。

    你可以做什么:

    使用 SQL: 将您的数据转换为数据库,并在此数据库上执行您的搜索查询。 不要遍历 RAM 中的所有对象。这会使您的应用程序性能非常低下。

    SQL 针对处理大量数据和对其执行查询进行了优化。

    阅读有关 Java 数据库管理的更多信息:JDBC Basics

    【讨论】:

      【解决方案4】:

      我打赌大部分时间都花在了阅读数据上。拥有 BufferedReader 应该会显着加快速度。

      【讨论】:

      • 读取文件后并行化流应该也有帮助。
      • 嗨!我已经使用 BufferReader 来读取数据。而且它的阅读速度非常快。它在 forEach 循环中停留了 20 分钟,我尝试遍历这 2000 万条记录并将它们分成 3 个列表,具体取决于当前对象中的值。所以基本上,我遍历整个列表一次,我有 if/else 条件,我检查对象中的某些字段是否等于“X”,“Y”或“Z”,并根据答案,分开这 2000 万条记录分为 3 个列表
      • 尝试流式传输您的收藏并将其元素过滤到 3 个列表中。做你的计算。计时步骤。这样您就可以知道时间是花在排序数据还是进行计算上。
      • 如果读取数据不是瓶颈,那么请使用分析器并找出是什么。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多