java从列表中删除与某些复制规则匹配的元素

时间:2013-09-09 09:34:40

标签: java

我有一个这样的清单:

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);

我需要根据行(Map)中3个元素中的2个(“section”,“index”)删除重复项。这就是我想要做的事情:

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仍然是同样的例外:

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:“解决方案”未按预期工作。现在选择了正确的答案。

6 个答案:

答案 0 :(得分:4)

除非您通过Iterator进行迭代,否则无法在迭代时添加/删除集合元素。

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

请参阅Iterator#remove(),了解如何在迭代时从集合中删除元素。

您可以像这样构建代码:

//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源代码):

  

此类的迭代器和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。

答案 1 :(得分:1)

如果您明确使用Iterator,则可以从中删除。您无法将其与'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;
        }
    }
}

对于无法精确构造内部迭代的情况,最简单的方法是在循环开始之前复制原始列表。只需重复复制即可确保稳定和安全。可靠的迭代。

答案 2 :(得分: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 );

答案 3 :(得分: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);
        }
    }
}

答案 4 :(得分: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 ;
      }

    }

    }

答案 5 :(得分:0)

这是一个简单的逻辑,不使用loopIterator

您可以达到以下相同的效果,

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); 
}