优化Java差异XML算法

时间:2017-04-21 15:58:06

标签: java xml

我目前正致力于一种能够区分两个XML文件的算法。但是,在处理长XML文件时,这种算法非常慢。你有任何想法如何优化它?

package comparison;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jdom2.Document;
import org.jdom2.Element;

public class XmlDiff {
    private ArrayList<String> differences;
    private Document oldXml;
    private Document newXml;
    private Document configuration;
    private ArrayList<String> idDefinitions;

    public XmlDiff(Document oldXml, Document newXml, Document configuration) {
        this.differences = new ArrayList<String>();
        this.oldXml = oldXml;
        this.newXml = newXml;
        this.configuration = configuration;
        this.idDefinitions = new ArrayList<String>();
        for (int i = 0; i < this.configuration.getRootElement().getChild("Definition_id").getChildren().size(); i++) {
            idDefinitions.add(this.configuration.getRootElement().getChild("Definition_id").getChildren().get(i).getAttributeValue("Id_def"));
        }
    }

    public Document getOldXml() {
        return this.oldXml;
    }

    public Document getNewXml() {
        return this.newXml;
    }

    public Document getConfiguration() {
        return this.configuration;
    }

    public ArrayList<String> getDifferences() {
        return differences;
    }

    public ArrayList<String> getIdDefinitions() {
        return this.idDefinitions;
    }

    public boolean diffNodeName(Node oldNode, Node newNode) {
        if (oldNode.getNodeName().toLowerCase() == null && newNode.getNodeName().toLowerCase() == null) {
            return false;
        }

        if (oldNode.getNodeName().toLowerCase() == null && newNode.getNodeName().toLowerCase() != null) {
            return true;
        }

        if (oldNode.getNodeName().toLowerCase() != null && newNode.getNodeName().toLowerCase() == null) {
            return true;
        }

        if (!oldNode.getNodeName().toLowerCase().equals(newNode.getNodeName().toLowerCase())) {
            return true;
        }
        return false;
    }

    public boolean diffNodeAttributes(Node oldNode, Node newNode) {
        HashMap<String, String> oldAttributesHashMap = oldNode.buildHashMapFromAttributes();
        HashMap<String, String> newAttributesHashMap = newNode.buildHashMapFromAttributes();

        Set oldEntrySet = oldAttributesHashMap.entrySet();
        Iterator oldIter = oldEntrySet.iterator();

        while (oldIter.hasNext()) {
            Map.Entry oldMe = (Map.Entry) oldIter.next();
            if (newAttributesHashMap.get(oldMe.getKey()) != null && oldAttributesHashMap.get(oldMe.getKey()).toLowerCase().equals(newAttributesHashMap.get(oldMe.getKey()).toLowerCase())) {
                oldIter.remove();
                oldAttributesHashMap.remove(oldMe.getKey());
                newAttributesHashMap.remove(oldMe.getKey());
            }
        }
        return !(oldAttributesHashMap.isEmpty() && newAttributesHashMap.isEmpty());
    }

    public void diffNodeValue(Node oldNode, String oldPath, Node newNode, String newPath) {
        if (oldNode.getValue() == null && newNode.getValue() != null) {
            this.getDifferences().add("value modified : " + oldPath + " } " + newPath + " : " + oldNode.getValue() + " changed to " + newNode.getValue());
        }

        if (oldNode.getValue() != null && newNode.getValue() == null) {
            this.getDifferences().add("value modified : " + oldPath + " } " + newPath + " : " + oldNode.getValue() + " changed to " + newNode.getValue());
        }

        if (!oldNode.getValue().equals(newNode.getValue())) {
            this.getDifferences().add("value modified : " + oldPath + " } " + newPath + " : " + oldNode.getValue() + " changed to " + newNode.getValue());
        }
    }

    public void compare(Node oldRoot, String oldPath, Node newRoot, String newPath) {
        if (oldRoot.hasChildren() && newRoot.hasChildren()) {
            String memoryOldPath = oldPath;
            String memoryNewPath = newPath;
            HashMap<Integer, Node> oldChildren = oldRoot.buildHashMapFromChildren();
            HashMap<Integer, Node> newChildren = newRoot.buildHashMapFromChildren();

            Set oldEntrySet = oldChildren.entrySet();
            Iterator oldIter = oldEntrySet.iterator();

            while (oldIter.hasNext()) {
                Map.Entry oldMe = (Map.Entry) oldIter.next();
                int actualIndex = 0;
                Set newEntrySet = newChildren.entrySet();
                Iterator newIter = newEntrySet.iterator();
                Node actualOldChild = oldChildren.get(oldMe.getKey());
                boolean matched = false;
                boolean valueChanged = false;
                boolean attributesChanged = false;

                while (!matched && newIter.hasNext()) {
                    Map.Entry newMe = (Map.Entry) newIter.next();
                    Node actualNewChild = newChildren.get(newMe.getKey());
                    if (actualOldChild.getNodeName().toLowerCase().equals(actualNewChild.getNodeName().toLowerCase())) {
                        if (actualOldChild.getHasSimilarSiblings() || actualNewChild.getHasSimilarSiblings()) {
                            if (actualOldChild.hasAttributes() || actualNewChild.hasAttributes()) {
                                if (!this.diffNodeAttributes(actualOldChild, actualNewChild)) {
                                    if (actualOldChild.hasChildren() && actualNewChild.hasChildren()) {
                                        int oldResult = this.findIdChild(actualOldChild.getElement().getChildren());
                                        if (oldResult != -1) {
                                            matched = actualOldChild.getElement().getChildren().get(oldResult).getValue().toLowerCase().equals(actualNewChild.getElement().getChildren().get(oldResult).getValue().toLowerCase());
                                        }
                                        else {
                                            matched = true;
                                        }
                                    } else {
                                        matched = true;
                                    }
                                }
                                else {
                                    attributesChanged = true;
                                }
                            } else {
                                if (actualOldChild.hasChildren() && actualNewChild.hasChildren()) {
                                    int oldResult = this.findIdChild(actualOldChild.getElement().getChildren());
                                    if (oldResult != -1) {
                                        matched = actualOldChild.getElement().getChildren().get(oldResult).getValue().toLowerCase().equals(actualNewChild.getElement().getChildren().get(oldResult).getValue().toLowerCase());
                                    }
                                    else {
                                        matched = true;
                                    }
                                }
                                else {
                                    matched = ((actualOldChild.getValue().toLowerCase() != null && actualNewChild.getValue().toLowerCase() != null&& actualOldChild.getValue().toLowerCase().equals(actualNewChild.getValue().toLowerCase()))|| (actualOldChild.getValue().toLowerCase() == null&& actualNewChild.getValue().toLowerCase() == null));
                                    valueChanged = ((actualOldChild.getValue().toLowerCase() != null && actualNewChild.getValue().toLowerCase() != null && !actualOldChild.getValue().toLowerCase().equals(actualNewChild.getValue().toLowerCase())) || (actualOldChild.getValue().toLowerCase() != null && actualNewChild.getValue().toLowerCase() == null) || (actualOldChild.getValue().toLowerCase() == null && actualNewChild.getValue().toLowerCase() != null));
                                }
                            }
                        }
                        else {
                            if (actualOldChild.hasAttributes()) {
                                if (!this.diffNodeAttributes(actualOldChild, actualNewChild)) {
                                    matched = true;
                                }
                                else {
                                    attributesChanged = true;
                                }
                            }
                            else {
                                matched = true;
                            }
                        }
                    }
                    actualIndex = (Integer) newMe.getKey();
                }

                if (matched || valueChanged || attributesChanged) {
                    oldRoot = actualOldChild;
                    newRoot = newChildren.get(actualIndex);
                    oldPath = oldPath + "/" + oldRoot.getNodeName() + "-" + oldMe.getKey();
                    newPath = newPath + "/" + newRoot.getNodeName() + "-" + actualIndex;
                    oldIter.remove();
                    newIter.remove();
                    oldChildren.remove(oldMe.getKey());
                    newChildren.remove(actualIndex);
                    if (matched) {
                        this.compare(oldRoot, oldPath, newRoot, newPath);
                    } else if (valueChanged) {
                        this.differences.add("value modified : " + oldPath + " } " + newPath + " : " + oldRoot.getValue() + " changed to " + newRoot.getValue());
                    }
                    else if(attributesChanged) {
                        this.differences.add("attributes changed : " + oldPath + " } " + newPath);
                    }
                    this.compare(oldRoot, oldPath, newRoot, newPath);
                    oldPath = memoryOldPath;
                    newPath = memoryNewPath;
                }
            }

            for (int i : oldChildren.keySet()) {
                this.getDifferences().add("deleted : " + oldPath + "/" + oldChildren.get(i).getNodeName() + "-" + i + " } " + newPath);
            }

            for (int j : newChildren.keySet()) {
                this.getDifferences().add("added : " + oldPath + " } " + newPath + "/" + newChildren.get(j).getNodeName() + "-" + j);
            }
        }

        else if ((!oldRoot.hasChildren() && newRoot.hasChildren()) || (oldRoot.hasChildren() && !newRoot.hasChildren())) {
            this.getDifferences().add("structure modified : " + oldPath + " } " + newPath);
        }

        else if (!oldRoot.hasChildren() && !newRoot.hasChildren()) {
            this.diffNodeValue(oldRoot, oldPath, newRoot, newPath);
        }
    }

    public int findIdChild(List<Element> children) {
        int result = -1;
        for (int i = 0; i < this.getIdDefinitions().size(); i++) {
            String name = this.getIdDefinitions().get(i);
            for (int k = 0; k < children.size(); k++) {
                if (children.get(k).getName().equals(name)) {
                    result = k;
                    break;
                }
            }
            if (result != -1) {
                break;
            }
        }
        return result;
    }
}

非常感谢你的帮助!

3 个答案:

答案 0 :(得分:1)

你有两个嵌套的while循环,你的大部分比较在嵌套逻辑中非常深。你基本上把它写成O(n * n)或O(n ^ 2)。

此外,您还会经历一些不必要的循环,将发现的差异移动到累积它们的数据结构中。实际上,这个类的大部分都偏离了将数据与算法分开的过程,这意味着每次想要获取数据时,都需要额外的堆栈帧来获取“数据”对象。 / p>

你经常要求小写进行比较,这违反了XML的精神,因为XML区分大小写,但假设你需要,在比较之前就停止这样做。它在堆上构造了太多新字符串。使用String.equalsIgnoreCase(...)可能会获得一点点提升。

另外,我会避免使用迭代器来减少堆上的压力。迭代器是将索引包装在List内的对象,然后该对象存储在堆上。这意味着在运行时额外的堆栈帧,以及更高的RAM使用率。重写使用面向对象的三节for循环可能会读得不那么干净,但可以加快执行速度。

更好的解决方案是首先索引一个树,索引的键是使用键的哈希搜索的微不足道的东西。然后在走另一棵树时,您可以快速查找是否存在预期值。这将是一次重大改写,但您应该能够将复杂性降低到O(n log n)性能配置文件,这将允许您更快地处理更大的文件。当然,有一个限制,最终O(n log n)可能不够快。

答案 1 :(得分:1)

您的代码正在使用DOM来解析XML,因此您需要在解析之前将整个文档读入内存。这比SAXStAX方法花费的时间和内存要多得多,这些方法用于逐个元素地读取和处理XML。

想象一下,您正在比较第一个标记中不同的两个巨大 xml文件。使用 DOM 方法,您将首先在内存中读取它们然后进行比较。使用 SAX / StAX 方法,xml处理器会通知您已达到某个元素,因此您可以将其值与第二个{{1中的相应元素(如果存在)进行比较,这将允许您比 DOM 方法更快地检测差异。

答案 2 :(得分:0)

很难确切地说是什么让你的代码变得如此缓慢,但是很快就会看到你有很多很多迭代器;如果您使用迭代器来浏览包含10,000个项目的xml文件,而另一个包含10,000个项目的xml文件,则需要100,000,000个计算才能执行。

我最近创建了一个类似的比较程序,但它不适用于XML文件。 我的过程首先是将所有项目放入多个HashMaps中,然后将它们全部包装到CachedDatabase类中。密钥以这样的方式生成,以便旧地图和新地图对于不同的项目具有相同的密钥,即使该值已经改变。我不知道这是否适用于您的场景。

我的建议是找出每个可比项目的最合适的数据结构。 HashMaps和HashSet很棒,因为查找时间是O(1)复杂度,这意味着我不必遍历整个地图来检查项目。

我会在一两个小时内回到这个问题,看看我能否为你更充分地回答这个问题。