在java中比较2个非常大的arraylists

时间:2017-01-12 08:13:21

标签: java arraylist out-of-memory

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

这些arraylist大小都是10万件,只需比较每件商品就会崩溃。

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的ram

对此事的任何意见都表示赞赏。

5 个答案:

答案 0 :(得分:3)

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

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

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

选项 2)将2个ArrayList()添加到一起,它们似乎是同一种对象。使对象可排序(可能通过ID),对单个列表进行排序。 (创建另一个问题)然后使用循环将现在排序的对象与其邻居进行比较。

答案 1 :(得分:3)

如果任何项目列表中的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的值,你只需要迭代这几个项而不是遍历整个列表。

关于OutOfMemory的修改

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

因此,如果你有一个1000大小的数组并且它已满,那么一个新的数组就是大小2000分配。在那一刻,需要3000个项目的内存(虽然不久之后会释放1000个)。

LinkedList只为你添加的每个项目分配内存(加上内存指向下一个和前一个元素)。

答案 2 :(得分:1)

您可以在集合界面中使用 removeAll 方法:)

rItems.removeAll(cItems);

如果您查看实现内部,该方法将使用equals进行比较...

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

答案 3 :(得分:1)

对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++;
    }
}

答案 4 :(得分:0)

我有同样的问题。所以我尝试使用LinkedList。 所以我有2个Linkedlist,可以包含多达350万的字符串记录。 然后我正在运行

setupFragment(HomeFragment.class, menu.findItem(R.id.menu_home));

以获得差异,但我的应用程序堆叠在此。

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