【问题标题】:Java count of items in an array (similar to a SQL aggregate function)数组中项目的 Java 计数(类似于 SQL 聚合函数)
【发布时间】:2010-12-14 02:16:26
【问题描述】:

我正在连接一个非常不灵活的套接字 API。它将返回如下行:

NAME, CITY, STATE, JOB, MONTH

但会有重复,因为它不进行任何聚合。我需要计算重复的行数(这在 SQL 中很容易,但据我所知,在 Java 中不是)。

示例源数据:

NAME,     CITY, STATE, JOB,         MONTH
John Doe, Denver, CO, INSTALLATION, 090301
John Doe, Denver, CO, INSTALLATION, 090301
John Doe, Denver, CO, INSTALLATION, 090301
Jane Doe, Phoenix, AZ, SUPPORT, 090301

目的:

    NAME,    CITY, STATE,          JOB,  MONTH, COUNT
John Doe,  Denver,    CO, INSTALLATION, 090301,   3
Jane Doe, Phoenix,    AZ,      SUPPORT, 090301,   1

我可以轻松地为大约 100,000 个返回行执行此操作,但我在一个月内处理大约 6000 万个返回行。有什么想法吗?

编辑:不幸的是,这些行没有返回排序......也没有通过 API 对它们进行排序的选项。我得到了一大堆需要汇总的东西。现在我使用一个 ArrayList 并执行 indexOf(new row) 来查找该项目是否已经存在,但是行数越多它就越慢。

编辑:为澄清起见,这只需要每月运行一次,在月底。谢谢大家的回复

【问题讨论】:

    标签: java count aggregation


    【解决方案1】:

    您可以使用 HashSet 来存储具有相同内容的前一行。 (假设您的 Row 对象实现了正确的 .hashValue() 和 .equals() 方法。

    可能是这样的:

    Set<Row> previousRows = new HashSet<Row>();
    List<Row> rowsInOrder = new LinkedList<Row>();
    

    然后在使用中(进一步假设您有 Row 类的 incrementCount() 方法):

    Row newRow = getNextRow();
    if(!previousRows.contains(newRow)){
        previousRows.put(newRow);
        rowsInOrder.add(newRow);
    } 
    previousRows.get(newRow).incrementCount();
    

    如果你不关心行进来的顺序,你可以去掉 List 而只使用 Set。

    【讨论】:

    • 几乎完全符合我的建议。
    • 考虑到 OP 需要处理的数据集的大小,将所有行存储在 HashSet 中可能会导致 OutOfMemoryException
    • 我会在星期一试试这个......它看起来就像我正在寻找的东西。我最大的问题是它在运行过程中变得越来越慢...... HashSet 应该快得多......
    • @Asaph,在 OP 中,jle 提到它们已经存储在 ArrayList 中。切换到 HashSet 不应引入任何显着的内存开销,并且如果需要顺序,将引用存储在 HashSet 和 ArrayList 中不应增加太多的每行开销。如果由于内存限制而需要开始寻找将数据外部化的解决方案,我建议使用 SQLite 或 Derby 等嵌入式数据库作为中间存储。
    • 我最终使用了 Dictionary/HashTable 而不是 HashSet,但您的想法肯定是正确的。
    【解决方案2】:

    您是否有足够的灵活性,或者这是一项足够重要的任务来投资Hadoop 之类的东西?有了这么大的数据,您想开始从“地图缩减”的思维方式来考虑它。

    【讨论】:

      【解决方案3】:

      您能否一次将所有数据放入内存中?如果你把它放在一个 ArrayList 中,听起来你可以。

      如果是这种情况,您可以只使用 MultiSet 的实现,例如 Google collections 中的那个

      然后,您可以将所有行插入到多重集中,如下所示

      Multiset<Row> rowMultiset = HashMultiset.create();
      for (Row row: rows) {
        rowMultiset.add(row);
      }
      

      您可以通过计数进行迭代,使用类似的方法:

      for (Multiset.Entry entry : rowMultiset.entrySet()) {
        System.out.println("row: "+entry.getElement()+", count: "+entry.getCount());
      }
      

      如果您不想使用外部库,您可以使用 HashMap 将行映射到整数来执行类似的操作。

      如果不是所有行都适合内存,我认为最简单的方法就是将数据插入数据库并进行查询。数据库针对无法装入内存的大型数据集进行设计和优化。

      【讨论】:

        【解决方案4】:

        返回的行是否总是排序的? IE。要分组的行总是一个接一个地返回吗?如果答案是肯定的:

        1) 初始化一个计数器。

        2) 跟踪您刚刚阅读的上一行并将其与当前行进行比较。如果相同,请增加您的计数器。如果不同,请使用当前计数器值记录您的行并重置计数器。

        3) 当你到达最后一条记录时,确保记录当前计数的行。

        此策略将允许您以流的形式读取大型数据集,并保持程序的内存占用较低,同时生成您所追求的更紧凑的聚合数据。

        【讨论】:

          【解决方案5】:

          我可以想到四种方法来做到这一点:

          • 如果您有足够的内存在内存中保存 6000 万行的表示(较少重复),请使用 HashMap&lt;Row, Integer&gt; 来表示计数。

          • 将行存储在 RDB 中,然后使用 SQL 进行聚合和计数。

          • 将行写入一个大文件并在计算之前使用经典合并排序 单遍中的行。

          • 使用 Hadoop 之类的工具将行分布在多台机器上。

          您预计将在一个月或更长时间内累积计数这一事实表明您需要考虑您的应用程序需要重新启动的可能性。这表明需要 RDB 或基于文件的解决方案。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-09-21
            • 2012-11-02
            • 1970-01-01
            • 2017-05-10
            • 2021-11-18
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多