我目前正致力于一种能够区分两个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;
}
}
非常感谢你的帮助!
答案 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,因此您需要在解析之前将整个文档读入内存。这比SAX或StAX方法花费的时间和内存要多得多,这些方法用于逐个元素地读取和处理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)复杂度,这意味着我不必遍历整个地图来检查项目。
我会在一两个小时内回到这个问题,看看我能否为你更充分地回答这个问题。