1

我有一个这样的列表:

List<Map<String, String>> list = new ArrayList<Map<String, String>>();
Map<String, String> row;

row = new HashMap<String, String>();
row.put("page", "page1");
row.put("section", "section1");
row.put("index", "index1");
list.add(row);

row = new HashMap<String, String>();
row.put("page", "page2");
row.put("section", "section2");
row.put("index", "index2");
list.add(row);

row = new HashMap<String, String>();
row.put("page", "page3");
row.put("section", "section1");
row.put("index", "index1");
list.add(row);

我需要根据行(地图)的 3 个元素(“节”、“索引”)中的 2 个相同来删除重复项。这就是我想要做的:

for (Map<String, String> row : list) {
    for (Map<String, String> el : list) {
        if (row.get("section").equals(el.get("section")) && row.get("index").equals(el.get("index"))) {
            list.remove(el);
        }
    }
}

它失败了java.util.ConcurrentModificationException。必须有另一种方法可以做到这一点,但我不知道如何。有任何想法吗?

更新:按照建议,我尝试使用迭代器,但仍然是相同的异常:

Iterator<Map<String, String>> it = list.iterator();
while (it.hasNext()) {
    Map<String, String> row = it.next();
    for (Map<String, String> el : list) {
        if (row.get("section").equals(el.get("section")) && row.get("index").equals(el.get("index"))) {
            list.remove(row);
        }
    }
}

UPDATE2:这失败了同样的例外:

Iterator<Map<String, String>> it = list.iterator();
while (it.hasNext()) {
    Map<String, String> row = it.next();
    Iterator<Map<String, String>> innerIt = list.iterator();
    while (innerIt.hasNext()) {
        Map<String, String> el = innerIt.next();
        if (row.get("section").equals(el.get("section")) && row.get("index").equals(el.get("index"))) {
            innerIt.remove();
            //it.remove(); //fails as well
        }
    }
}

更新 3,解决方案:非常简单:

for (int i = 0; i < list.size(); i++) {
    for (int j = 0; j < list.size(); j++) {
        if (list.get(i).get("section").equals(list.get(j).get("section")) && list.get(i).get("index").equals(list.get(j).get("index"))) {
            list.remove(i);
        }
    }
}

更新 4: “解决方案”没有按预期工作。现在选择正确答案。

4

6 回答 6

4

迭代时不能添加/删除集合的元素,除非通过Iterator进行迭代。

请参阅Collection#iterator()以在您的地图上获取迭代器。

请参阅Iterator#remove()以了解如何在对其进行迭代时从 Collection 中删除元素。

您可以像这样构造您的代码:

//Get an iterator on your list.
Iterator<Map<String, String>> itr = list.iterator();

//iterate
while(itr.hasNext()) {
  Map<String, String> elt= itr.next();
  if(isDuplicate(list, elt)) {
    itr.remove();
  }
}

这是查找是否有重复项的方法示例:

public boolean isDuplicate(List<Map<String, String>> list, Map<String, String> map){
  //Count the occurences of the searched element.
  int cpt = 0;

  /*
   * Here, another iterator is implicitly created.
   * It is not the same as in the main loop. 
   * That's why I avoid the ConcurrentModificationException.
   */
  for(Map<String, String> m : list) {
    if(m.get("section").equals(map.get("section")) && m.get("index").equals(map.get("index"))) {
      cpt++;
    }
  }
  //If the element is found twice, then it is a duplicate.
  return cpt == 2;
}

以下是 ArrayList#remove() 方法的 Javadoc 摘录(来自 Sun JDK 来源):

此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时间对列表进行结构修改,除了通过迭代器自己的 remove 或 add 方法之外的任何方式,迭代器将抛出 ConcurrentModificationException。因此,面对并发修改,迭代器快速而干净地失败,而不是在未来不确定的时间冒任意的、非确定性的行为。

为了进一步了解迭代器的工作原理,让我们阅读 ArrayList 迭代器的 Sun JDK 源代码。这是 ArrayList.java 中的一个内部类:

private class Itr implements Iterator<E> {
  int cursor;       // index of next element to return
  int lastRet = -1; // index of last element returned; -1 if no such
  int expectedModCount = modCount;

在这里,我们可以看到当实例化(使用 Collection#iterator() )时,迭代器初始化了一个expectedModCount(modCount = 修改计数)。这里,modCount是类 ArrayList 的一个属性。

每次在迭代器 ( next(), previous(), add(), remove()) 上调用方法时,都会调用此方法:

final void checkForComodification() {
  if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}

这是抛出ConcurrentModificationException的方法!

每次对列表进行修改时,ArrayList 都会更新modCount. 因此,如果您修改没有迭代器的列表,modCount则变为 != expectedModCount。在下一次调用迭代器的任何方法时,您会得到异常。

当您使用 for-each 循环时,您会隐式创建一个迭代器并在每个循环结束时调用 next()。

每次通过迭代器中的方法修改列表时,expectedModCount都会更新为modCount,从而避免 ConcurrentModificationException。

于 2013-09-09T09:38:23.183 回答
1

如果显式使用迭代器,则可以从中删除。您将无法将其与“foreach”循环或其他迭代器结合使用,除非在这种情况下,一旦找到匹配项,内部循环就会结束。

[注意:我修复了条件,因为它不排除自匹配。]

Iterator<Map<String,String>> outerIt = list.iterator();
while (outerIt.hasNext()) {
    Map<String,String> outer = outerIt.next();

    for (Map<String, String> inner : list) {
        if ((inner != outer) && outer.get("section").equals(inner.get("section")) && outer.get("index").equals(inner.get("index"))) {
            // Match;  de-dup.
            //   -- no longer iterating the 'inner' loop, so we don't need a copy.
            outerIt.remove();
            break;
        }
    }
}

对于无法精确构建内部迭代的情况,最简单的方法是在循环开始之前复制原始列表,只需迭代副本以保证稳定可靠的迭代。

于 2013-09-09T09:43:25.990 回答
0

保留您希望删除的元素列表并在之后删除它们。

List<Map<String, String> removeList = new List<Map<String, String>();
for (Map<String, String> row : list) 
    for (Map<String, String> el : list)
        if (row.get("section").equals(el.get("section")) && row.get("index").equals(el.get("index")))
            removeList.add( el );


 for( Map< String, String > i : removeList )
     list.remove( i );
于 2013-09-09T09:42:58.693 回答
0

一种方法是复制您尝试修改的 HashMap。遍历副本并更改原始副本。

尝试这个...

Map<String, String> copyOfList = new HashMap<String, String>(list);

for (Map<String, String> row : copyOfList ) {
    for (Map<String, String> el : copyOfList ) {
        if (row.get("section").equals(el.get("section")) && row.get("index").equals(el.get("index"))) {
            list.remove(el);
        }
    }
}
于 2013-09-09T09:43:06.053 回答
0

您可以使用传统的 for 循环而不是使用其他循环。

   for(int i=0;i<list.size();i++)
    {

    for(int j=0;j<list.size();j++)
    {

     if (list.get(i).get("section").equals(list.get(j).get("section")) && list.get(i).get("index").equals(list.get(j).get("index"))) {

            list.remove(j);
            j -= 1 ;
      }

    }

    }
于 2013-09-09T09:58:09.153 回答
0

Here is a simple logic without using loop or Iterator,

You can achieve the same as below,

public static List<Map<String, String>> removeDuplicate(
        List<Map<String, String>> list) {

    Set<Map<String, String>> set = new TreeSet<Map<String, String>>(
            new Comparator<Map<String, String>>() {
                @Override
                public int compare(Map<String, String> o1,
                        Map<String, String> o2) {

                    // Your equals condition
                    if (o1.get("section").equals(o2.get("section"))
                            && o1.get("index").equals(o2.get("index")))
                        return 0;
                    return -1;
                }
            });
    set.addAll(list);     
    // convert back to list and return 
    return new ArrayList<Map<String, String>>(set); 
}
于 2013-09-09T11:08:11.490 回答