如何避免基于递归迭代器从LinkedList中删除时出现ConcurrentModificationException?

时间:2019-02-14 10:32:17

标签: java algorithm data-structures tree

我一直在尝试通过将子级分配给其父级然后通过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<>(allOrganizations)函数来解决了这个问题,但是副本部分要花O(n)的时间,我不想那。

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

我想利用LinkedList O(1)的删除功能,因此每次我添加一个孩子并重复出现时,列表都会变小。

我还通过将各个节点标记为可见并将基例设置为HashSet来成功实现了hashSet.size() == allOrganizations.size()的解决方案,但是我仍然在相同大小的List上重复执行此操作,因此别帮我。

是否有任何方法可以实现我使用LinkedList O(1)删除的目标,或者是否有更有效的替代方法?

1 个答案:

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

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