【问题标题】:Comparing 2 very large arraylists in java比较java中的2个非常大的数组列表
【发布时间】:2017-01-12 08:13:21
【问题描述】:

当您需要相互比较 2 个非常大的数组列表时,正确的方法是什么?

这些数组列表的大小都是 100,000 个项目,并且在简单地比较每个项目时肯定会崩溃。

for (CItem c : cItems) {
        for (CItem r : rItems) {
            if (c.getID().equals(r.getID())) {
                Mismatch m = compareItems(c, r);
                if (m != null) {
                    mismatches.add(m);
                }
            }
        }
    }

现在我不能 100% 确定垃圾收集在这种情况下是如何工作的,但我们得到的错误是:

java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:3664) ~[na:1.8.0_73]
at java.lang.String.<init>(String.java:207) ~[na:1.8.0_73]
at java.lang.StringBuilder.toString(StringBuilder.java:407) ~[na:1.8.0_73]

java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.Arrays.copyOf(Arrays.java:3181) ~[na:1.8.0_73]
at java.util.ArrayList.grow(ArrayList.java:261) ~[na:1.8.0_73]
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) ~[na:1.8.0_73]
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) ~[na:1.8.0_73]
at java.util.ArrayList.add(ArrayList.java:458) ~[na:1.8.0_73]

到目前为止可能的解决方案是

  • 将每个列表拆分为最多 x 个项目并比较这些多个列表(有点复杂)
  • 创建一个新数据库并查询每个项目(这会很慢而且现在不可行)
  • 购买 200 GB 内存

我们将不胜感激。

【问题讨论】:

  • 问题不在于您的比较,而是在发现不匹配时添加到新的 ArrayList
  • 什么是 compareItems(c, r) ?
  • 您可以尝试打印在异常之前添加了多少项目吗?
  • 其中一个列表中可以包含多少个具有相同 id 的项目?嵌套循环方法看起来也很昂贵,如何按 id 对列表进行排序,然后一次性遍历它们呢?
  • 200000 个项目不是问题。 100000*100000 是。

标签: java arraylist out-of-memory


【解决方案1】:

如果任何项目列表中的 ID 是唯一的,您可以使用 Map 作为您的 rItems,并以 ID 作为键。

Map<Long, CItem> rItemMap = new HashMap<>(rItems.size());
for (CItem r : rItems) {
    rItemMap.put(r.getID(), r);
}

现在您可以直接检查具有相同 ID 的 rItem:

for (CItem c : cItems) {
    CItem r = rItemMap.get(c.getID());
    if (r != null) {
        Mismatch m = compareItems(c, r);
        if (m != null) {
            mismatches.add(m);
        }
    }
}

即使 ID 不是唯一的,您仍然可以使用 Map,您只需将具有该 ID 的所有项目的列表作为一个 Map.Entry 的值,您只需要遍历这几个items 而不是遍历整个列表。

编辑 OutOfMemory

我刚刚从您的异常中看到,您正在使用ArrayList。改用LinkedList 可能会有所帮助,因为 ArrayList 基于(固定大小)数组,当该数组被填满时,会分配一个新的更大的数组,并将旧数组中的数据复制到新数组中然后被释放。

因此,如果您有一个大小为 1000 的数组并且它已满,则一个新数组例如分配大小 2000。那时,需要 3000 个项目的内存(尽管不久之后释放 1000 个)。

LinkedList 只是为您添加到其中的每个项目分配内存(加上指向下一个和上一个元素的内存)。

【讨论】:

  • 听起来很有趣,一定会试试这个
  • 效果很好而且速度很快,不幸的是我发现包含 100,000 行的记录集也崩溃了。不幸的是,这无法通过哈希图解决..
  • 尝试使用 LinkedList 而不是 ArrayList - 请参阅我编辑的答案
  • @Ridcully 使用 LinkedList 不太可能有帮助。它的每个元素开销是 ArrayList 的 6 倍,因此使用 LL 会比使用 AL 更快地耗尽空间,甚至考虑到 AL 在调整大小期间的瞬时内存使用情况。
  • @woodsprite 查看之前的评论。
【解决方案2】:

看起来你想看看 2 个具有相同 ID 的对象在以其他方式比较时是否相同。

这里可能的问题是您检查了 100.000 x 100.000 个对象。更糟糕的是,您只需将它们添加到新列表中......

选项 1) 您没有说明您是如何创建 ArrayList()s 的。如果您从数据库中获取对象,您可能只是查询它。 (这些都擅长,即使你不擅长)

选项2) 将2个ArrayList()加在一起,它们看起来是同一种对象。使对象可排序(可能按 ID),对单个列表进行排序。 (产生另一个问题)然后使用循环将现在排序的对象与其邻居进行比较。

【讨论】:

  • 数据库非常大并且优化得非常厉害,查询单个项目将不起作用(现在也不允许)。数组列表是通过查询视图创建的。选项 2 听起来很有希望,我会检查一下。
【解决方案3】:

你可以在收藏界面使用removeAll方法:)

rItems.removeAll(cItems);

如果您查看实现的内部,该方法也会使用等号进行比较...

这种方法可以让您从每个列表中获取与另一个不匹配的项目。

【讨论】:

  • 有趣但不可能,因为不幸的是,没有两个对象会完全相同
  • @woorsprite 你能不能基于compareItems() 为那些项目实现equals?或者,如果不是,是集合的比较器?
【解决方案4】:

对 2 个列表进行排序,然后按顺序比较它们。排序成本O(n log n) 并比较成本O(n)

Comparator<CItem> idComparator = new Comparator<CItem>() {
    @Override
    public int compare(CItem i1, CItem i2) {
        // Implementation depends on the type of CItem ID:
        // if ID is an integer or double, maybe you need
        // return i1.getID() - i2.getID();
        return i1.getID().compareTo(i2.getID());
    }
});

Collections.sort(cItems, idComparator);
Collections.sort(rItems, idComparator);

int minLen = Math.min(cItems.size(), rItems.size());
for (int i = 0, j = 0; i < minLen && j < minLen; ) {

    CItem c = cItems.get(i);
    CItem r = rItems.get(j);

    // c.getID().equals(r.getID())
    if (idComparator.compare(c, r) == 0) {
        Mismatch m = compareItems(c, r);
        if (m != null) {
            mismatches.add(m);
        }
        i++;
        j++;

    // item c's ID does not exist in list rItems
    } else if (idComparator.compare(c, r) < 0) {
        i++;

    // item r's ID does not exist in list cItems
    } else {
        j++;
    }
}

【讨论】:

    【解决方案5】:

    我也有同样的问题。所以我尝试使用LinkedList。 所以我有 2 个可以包含多达 350 万条字符串记录的链表。 然后我就跑了

    LinkedList diflist= (LinkedList) ListUtils.subtract(sourceList, targetList);
    

    要获得差异,但我的应用程序堆栈在此之上。

    那么有什么好的算法来比较列表吗?

    【讨论】:

    • 您已将此作为答案发布,但您基本上已经重新提出了您的问题 stackoverflow.com/questions/41723028/… 。这可能是因为有人将您的问题作为此问题的副本关闭。但我不认为它们实际上是相同的问题。我重新打开了你的问题;请回复那里的 cmets。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-02
    相关资源
    最近更新 更多