【问题标题】:Removing DOM nodes when traversing a NodeList遍历 NodeList 时移除 DOM 节点
【发布时间】:2010-11-25 08:04:17
【问题描述】:

我即将删除 XML 文档中的某些元素,使用如下代码:

NodeList nodes = ...;
for (int i = 0; i < nodes.getLength(); i++) {
  Element e = (Element)nodes.item(i);
  if (certain criteria involving Element e) {
    e.getParentNode().removeChild(e);
  }
}

这会干扰 NodeList 的正确遍历吗?这种方法还有其他注意事项吗?如果这是完全错误的,那么正确的做法是什么?

【问题讨论】:

    标签: java xml dom


    【解决方案1】:

    在循环时删除节点会导致不良结果,例如错过或重复的结果。这甚至不是同步和线程安全的问题,但如果节点被循环本身修改。在这种情况下,大多数 Java 的迭代器都会抛出 ConcurrentModificationException,这是 NodeList 不考虑的。

    可以通过减小 NodeList 大小并同时减小迭代指针来修复它。仅当我们为每个循环迭代执行一个删除操作时,才能使用此解决方案。

    NodeList nodes = ...;
    for (int i = nodes.getLength() - 1; i >= 0; i--) {
      Element e = (Element)nodes.item(i);
       if (certain criteria involving Element e) {
        e.getParentNode().removeChild(e);
      }
    }
    

    【讨论】:

    • 哇...有史以来最好的答案...这么多事情只是遍历 for 循环的方式!
    【解决方案2】:

    因此,鉴于在遍历 NodeList 时删除节点会导致 NodeList 被更新以反映新的现实,我假设我的索引将变得无效并且这将不起作用。

    因此,似乎解决方案是在遍历期间跟踪要删除的元素,然后在不再使用 NodeList 时将它们全部删除。

    NodeList nodes = ...;
    Set<Element> targetElements = new HashSet<Element>();
    for (int i = 0; i < nodes.getLength(); i++) {
      Element e = (Element)nodes.item(i);
      if (certain criteria involving Element e) {
        targetElements.add(e);
      }
    }
    for (Element e: targetElements) {
      e.getParentNode().removeChild(e);
    }
    

    【讨论】:

    • 您为什么觉得有必要这样做?您的标准是否取决于元素的兄弟姐妹?如果是(换句话说,如果您需要保留兄弟姐妹),则保留一个 List(不需要 Set,不会有重复项)。
    • 标准不依赖于兄弟姐妹,但如果我理解上面的答案,如果我删除 7 个节点中的 5 个,突然我的 NodeList 中只有 6 个节点,而我的 for 循环将有错误的索引,跳过一个节点,然后前进到列表的末尾。如果我有误解,请纠正我。
    • 删除节点的顺序无关紧要,是吗?
    • 好的,我明白你现在在说什么了。倒数。
    • 啊,我明白了!所以我只需要将 for 循环更改为 for (int i = nodes.getLength() - 1; i >= 0; i--) ,然后我就不需要集合了吗?完全有道理。发布它,重新解释为什么原来的不起作用,我会把你标记为接受的答案。 :)
    【解决方案3】:

    根据 DOM 规范,调用 node.getElementsByTagName("...") 的结果应该是“实时的”,即对 DOM 树所做的任何修改将反映在 NodeList 对象中。好吧,对于符合要求的实现,那就是......

    NodeList 和 NamedNodeMap 对象在 DOM 是活的;也就是说,更改为 基础文档结构是 反映在所有相关的 NodeList 和 NamedNodeMap 对象。

    (DOM Specification)

    因此,当您修改树结构时,符合要求的实现将更改 NodeList 以反映这些更改。

    【讨论】:

    • 那么这意味着我的索引在遍历过程中变得无效,对吧?
    • @Dirk,除了引用有关 DOM NodeList 规范和 Java 实现它的重要信息之外......这个答案没有提供关于这个问题的结论性陈述......
    【解决方案4】:

    Practical XML 库现在包含 NodeListIterator,它包装了 NodeList 并提供完整的迭代器支持(这似乎比发布我们在 cmets 中讨论的代码更好)。如果您不想使用完整的库,请随意复制该类:http://practicalxml.svn.sourceforge.net/viewvc/practicalxml/trunk/src/main/java/net/sf/practicalxml/util/NodeListIterator.java?revision=125&view=markup

    【讨论】:

      【解决方案5】:

      根据 DOM Level 3 Core 规范,

      调用node.getElementsByTagName("...") 方法的结果将是对“liveNodeList 类型的引用。

      DOM 中的 NodeList 和 NamedNodeMap 对象是活动的;也就是说,对底层文档结构的更改会反映在所有相关的 NodeList 和 NamedNodeMap 对象中。 ...更改会自动反映在 NodeList 中,无需用户采取进一步行动。

      1.1.1 The DOM Structure Model, para. 2

      JavaSE 7 符合 DOM Level 3 规范:它实现了 live NodeList 接口并将其定义为一个类型;它在Interface Element 上定义并公开getElementsByTagName 方法,该方法返回live NodeList 类型。


      参考文献

      W3C - Document Object Model (DOM) Level 3 Core Specification - getElementsByTagName

      JavaSE 7 - Interface Element

      JavaSE 7 - NodeList Type

      【讨论】:

        【解决方案6】:

        旧帖子,但没有任何标记为答案。我的方法是从头开始迭代,即

        for (int i = nodes.getLength() - 1; i >= 0; i--) {
            // do processing, and then
            e.getParentNode().removeChild(e);
        }
        

        有了这个,你不必担心 NodeList 在你删除的时候会变短。

        【讨论】:

          【解决方案7】:

          如前所述,删除一个元素会减小列表的大小,但计数器仍在增加 (i++):

          [element 1] <- Delete 
          [element 2]
          [element 3]
          [element 4]
          [element 5]
          
          [element 2]  
          [element 3] <- Delete
          [element 4]
          [element 5]
          --
          
          [element 2]  
          [element 4] 
          [element 5] <- Delete
          --
          --
          
          [element 2]  
          [element 4] 
          --
          --
          --
          

          我认为最简单的解决方案是删除循环中的 i++ 部分,并在未删除迭代元素时根据需要执行此操作。

          NodeList nodes = ...;
          for (int i = 0; i < nodes.getLength();) {
            Element e = (Element)nodes.item(i);
            if (certain criteria involving Element e) {
              e.getParentNode().removeChild(e);        
            } else {
              i++;
            }
          }
          

          当迭代元素被删除时,指针停留在同一个位置。列表会自行移动。

          [element 1] <- Delete 
          [element 2]
          [element 3]
          [element 4]
          [element 5]
          
          [element 2] <- Leave
          [element 3]
          [element 4]
          [element 5]
          --
          
          [element 2] 
          [element 3] <- Leave
          [element 4]
          [element 5]
          --
          
          [element 2] 
          [element 3] 
          [element 4] <- Delete
          [element 5]
          --
          
          [element 2] 
          [element 3] 
          [element 5] <- Delete
          --
          --
          
          [element 2] 
          [element 3] 
          --
          --
          --
          

          【讨论】:

            【解决方案8】:

            最后,您必须更新项目路径中的 XML 文件。

            TransformerFactory transFactory = TransformerFactory.newInstance();
            Transformer transformer = transFactory.newTransformer();
            DOMSource source = new DOMSource(documentoXml);
            StreamResult result = new StreamResult(new File(path + "\\resources\\xml\\UsuariosFile.xml"));
            transformer.transform(source, result);
            

            如果你不放这些行,你的文件将不会被更新

            【讨论】:

              猜你喜欢
              • 2020-04-05
              • 2017-09-18
              • 2020-05-07
              • 2011-01-31
              • 1970-01-01
              • 1970-01-01
              • 2015-05-12
              • 2019-11-21
              • 2018-09-02
              相关资源
              最近更新 更多