【问题标题】:How to avoid ConcurrentModificationException on recursive iterator based removal from LinkedList?如何避免基于递归迭代器从 LinkedList 中删除的 ConcurrentModificationException?
【发布时间】:2019-02-14 10:32:17
【问题描述】:

我一直在尝试通过将孩子分配给其父母然后通过it.remove() 删除它来有效地创建树:

for (OgOrganizationTreeDTO parent : parents) {
    setChildren(parent, organizationTree);
}

这里是setChildren函数的实现

private void setChildren(OgOrganizationTreeDTO root, List<OgOrganizationTreeDTO> allOrganizations) {
    if (allOrganizations.isEmpty()) {
        return ;
    }
    Iterator<OgOrganizationTreeDTO> it = allOrganizations.iterator();
    while (it.hasNext()) {
        OgOrganizationTreeDTO potentialChild = it.next();
        if (potentialChild.getIdParentId() != null && potentialChild.getIdParentId().equals(root.getId())) {
            root.addChild(potentialChild);
            it.remove();
            setChildren(potentialChild, allOrganizations);
        }
    }
}

我正在使用 LinkedList,我得到一个 ConcurrentModificationException。我已经通过将allOrganizations 的副本传递给递归setChildren 函数解决了这个问题,就像new LinkedList&lt;&gt;(allOrganizations) 一样,但是复制部分需要O(n) 时间,我不希望这样。

我也尝试过使用LinkedBlockingQueue,但我发现删除需要O(n) 时间。

我想利用 LinkedList O(1) 删除,所以每次我添加一个孩子并在它上面重复时,列表会变小。

我还成功地使用HashSet 实现了一个解决方案,方法是将各个节点标记为可见,并将基本情况设置为hashSet.size() == allOrganizations.size(),但我仍然在相同大小的列表上重复出现,所以这对我没有帮助其他。

有什么方法可以实现我使用LinkedListO(1) remove 的目标,或者有没有更有效的替代方法?

【问题讨论】:

  • 您可以尝试使用ListIterator
  • 或者,如果您担心性能,您可以在不递归的情况下做到这一点 - 使用大哈希图来存储所有已处理的实体,您可以在列表上进行 2 次迭代
  • loop on list with remove的可能重复

标签: java algorithm data-structures tree


【解决方案1】:

好吧,我不知道如何使用递归,因为您实际上在每个递归调用中创建了一个新的迭代器(allOrganizations.iterator() - 创建新的迭代器实例)。因此,当您调用 delete 时,您会修改其他迭代器的集合,这就是它引发该异常的原因。

  • 一种解决方案是使用一些允许并发修改的 CopyOnWriteList,但它只是传递列表的副本,而不是修改同一个,因此会占用额外的内存。

  • 另一种解决方案是向 OgOrganizationTreeDTO 类添加一些属性,以标记该行是否已被处理。在这种情况下,您只需将其标记为已处理,而不是从列表中删除该项目。

  • 但无论如何,既然您询问的是性能和大 O,那么我可以为您提供另一个具有 O(n) 复杂度的解决方案。当然,使用更多内存需要权衡取舍,但这是标准内存与复杂性问题...

    private static void setChildrenMap(OgOrganizationTreeDTO root, 
         List<OgOrganizationTreeDTO> allOrganizations) {
    
      Map<Integer, OgOrganizationTreeDTO> organizationsMap = new HashMap<>();
      organizationsMap.put(root.getId(), root);
    
      for (OgOrganizationTreeDTO element : allOrganizations) {
        organizationsMap.put(element.getId(), element);
      }
    
      for (OgOrganizationTreeDTO element : allOrganizations) {
         organizationsMap.get(element.getParentId()).addChild(element);
      }
    
    }
    

基本上,从 hashmap 中获取元素是一个常数时间,因此我们使用它来查找所需的父元素。如果您期望元素包含错误数据,您可能需要添加一些检查,因为 organizationsMap.get(element.getParentId()) 可能会返回 null

【讨论】:

  • 谢谢。确实非常优雅的解决方案。
猜你喜欢
  • 2013-08-29
  • 2018-05-09
  • 2016-07-15
  • 2014-02-25
  • 2017-11-27
  • 1970-01-01
  • 2014-02-10
  • 2012-10-31
相关资源
最近更新 更多