如何在Java中对XML文件进行小修改

3
我正在尝试更改一个大型(5mb)XML文件中的单个值。我总是知道该值将在前10行中,因此我不需要读入文件的99%。然而,在Java中进行部分XML读取似乎非常棘手。 在这张图片中,您可以看到我需要访问的单个值。
我已经阅读了很多关于Java中处理XML和最佳实践的文章。但是,在这种情况下,我不确定最好的方法是什么 - DOM、STAX或SAX解析器似乎都有不同的最佳用例场景 - 我不确定哪种最适合这个问题。因为我只需要编辑一个值。
也许,我甚至不应该使用XML解析器,而应该使用正则表达式,但似乎在XML上使用正则表达式是一个相当糟糕的想法
希望有人能指点我正确的方向,谢谢!

2
其实并不存在所谓的“部分读取”,因为如果你没有整个文件,那么很可能这个文件格式不正确,因此无法解析,如果无法解析,则无法访问其属性。对于这样一个小的编辑,最好的方法可能是将整个文件作为字符串加载(比尝试反序列化快得多),然后进行字符串替换/模式搜索。 - Wobbles
2
@Wobbles,那并不是真的。SAX和StAX解析器是专门为你不想将整个文档加载到内存中的情况而构建的。 - ug_
当然,你可以进行部分读取。但是没有部分写入的概念(除非是追加到文件末尾)。 - Kayaman
部分读取很容易 - 在这种情况下,StAX 将完美地工作。问题在于写入 - 没有部分写入这样的事情; 例如,如果您将“10”更改为“100”,则必须将文件中的所有字节向前移动一个 ASCII 字符。因此,我建议您使用 SAX 或 StAX 将文件从一个位置流式传输到另一个位置,更改该单个值。绝对不要将整个 String 读入内存。 - Boris the Spider
3
@Wobbles, 那是错误的,也是一个可怕的想法。 - Boris the Spider
2个回答

2

相对来说,我会选择DOM而不是SAX或StAX,因为它的API相对简单。是的,有一些样板代码需要填充DOM,但一旦你过了那个阶段,它就相当直观了。

话虽如此,如果你的XML源文件达到100或1000兆字节,其中一个流式API将更适合。目前为止,5MB不是我认为的大型数据集,所以继续使用DOM并完成它:

import java.io.File;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;

public class ChangeVersion
{
    public static void main(String[] args)
            throws Exception
    {
        if (args.length < 3) {
            System.err.println("Usage: ChangeVersion <input> <output> <new version>");
            System.exit(1);
        }

        File inputFile = new File(args[0]);
        File outputFile = new File(args[1]);
        int updatedVersion = Integer.parseInt(args[2], 10);

        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = domFactory.newDocumentBuilder();
        Document doc = docBuilder.parse(inputFile);

        XPathFactory xpathFactory = XPathFactory.newInstance();
        XPath xpath = xpathFactory.newXPath();
        XPathExpression expr = xpath.compile("/PremiereData/Project/@Version");

        NodeList versionAttrNodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);

        for (int i = 0; i < versionAttrNodes.getLength(); i++) {
            Attr versionAttr = (Attr) versionAttrNodes.item(i);
            versionAttr.setNodeValue(String.valueOf(updatedVersion));
        }

        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();

        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.transform(new DOMSource(doc), new StreamResult(outputFile));
    }
}

1
好的,完整且可运行的示例加1分。我会谨慎地将5mb甚至更多的数据读入DOM,但我相信你是正确的——从流式传输中获得的性能增益可能被学习曲线所掩盖…… - Boris the Spider
完全正确,程序甚至几乎没有卡顿。不知道我在担心什么 - 这个工作非常好,谢谢! - nmu

2
您可以使用StAX解析器在读取XML时编写它。在解析过程中,您可以替换内容。使用StAX解析器将仅在任何给定时间将XML的部分包含在内存中。
public static void main(String [] args) throws Exception {
    final String newProjectId = "888";

    File inputFile = new File("in.xml");
    File outputFile = new File("out.xml");
    System.out.println("Reading " + inputFile);
    System.out.println("Writing " + outputFile);

    XMLInputFactory inFactory = XMLInputFactory.newInstance();
    XMLEventReader eventReader = inFactory.createXMLEventReader(new FileInputStream(inputFile));
    XMLOutputFactory factory = XMLOutputFactory.newInstance();
    XMLEventWriter writer = factory.createXMLEventWriter(new FileWriter(outputFile));
    XMLEventFactory eventFactory = XMLEventFactory.newInstance();


    boolean useExistingEvent; // specifies if we should use the event right from the reader
    while (eventReader.hasNext()) {
        XMLEvent event = eventReader.nextEvent();
        useExistingEvent = true;

        // look for our Project element
        if(event.getEventType() == XMLEvent.START_ELEMENT) {
            // read characters
            StartElement elemEvent = event.asStartElement();
            Attribute attr = elemEvent.getAttributeByName(QName.valueOf("ObjectID"));
            // check to see if this is the project we want 
            // TODO: put what logic you want here
            if("Project".equals(elemEvent.getName().getLocalPart()) && attr != null && attr.getValue().equals("1")) {
                Attribute versionAttr = elemEvent.getAttributeByName(QName.valueOf("Version"));

                // we need to make a list of new attributes for this element which doesnt include the Version a
                List<Attribute> newAttrs = new ArrayList<>(); // new list of attrs
                Iterator<Attribute> existingAttrs = elemEvent.getAttributes();
                while(existingAttrs.hasNext()) {
                    Attribute existing = existingAttrs.next();
                    // copy over everything but version attribute
                    if(!existing.getName().getLocalPart().equals("Version")) {
                        newAttrs.add(existing);
                    }
                }
                // add our new attribute for projectId
                newAttrs.add(eventFactory.createAttribute(versionAttr.getName(), newProjectId));

                // were using our own event instead of the existing one
                useExistingEvent = false;
                writer.add(eventFactory.createStartElement(elemEvent.getName(), newAttrs.iterator(), elemEvent.getNamespaces()));
            }
        }

        // persist the existing event.
        if(useExistingEvent) {
            writer.add(event);
        }

    }
    writer.close();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接