【问题标题】:How to improve splitting xml file performance如何提高拆分xml文件的性能
【发布时间】:2011-09-13 21:44:07
【问题描述】:

我看到了很多关于将 XML 文件拆分成更小的块的帖子/博客/文章,并决定创建自己的,因为我有一些自定义要求。这就是我的意思,请考虑以下 XML:

<?xml version="1.0" encoding="UTF-8" standalone="no" ?> 
<company>
 <staff id="1">
    <firstname>yong</firstname>
    <lastname>mook kim</lastname>
    <nickname>mkyong</nickname>
    <salary>100000</salary>
   </staff>
 <staff id="2">
    <firstname>yong</firstname>
    <lastname>mook kim</lastname>
    <nickname>mkyong</nickname>
    <salary>100000</salary>
   </staff>
 <staff id="3">
    <firstname>yong</firstname>
    <lastname>mook kim</lastname>
    <nickname>mkyong</nickname>
    <salary>100000</salary>
   </staff>
 <staff id="4">
    <firstname>yong</firstname>
    <lastname>mook kim</lastname>
    <nickname>mkyong</nickname>
    <salary>100000</salary>
   </staff>
 <staff id="5">
    <firstname>yong</firstname>
    <lastname>mook kim</lastname>
    <salary>100000</salary>
   </staff>
</company>

我想将此 xml 拆分为 n 个部分,每个部分包含 1 个文件,但 staff 元素必须包含 nickname ,如果它不存在我不想要它。所以这应该产生 4 个 xml 拆分,每个包含从 1 到 4 的员工 ID。

这是我的代码:

public int split() throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(inputFilePath)));

        String line;
        List<String> tempList = null;

        while((line=br.readLine())!=null){
            if(line.contains("<?xml version=\"1.0\"") || line.contains("<" + rootElement + ">") || line.contains("</" + rootElement + ">")){
                continue;
            }

            if(line.contains("<"+ element +">")){
                tempList = new ArrayList<String>();
            }
            tempList.add(line);

            if(line.contains("</"+ element +">")){
                if(hasConditions(tempList)){
                    writeToSplitFile(tempList);
                    writtenObjectCounter++;
                    totalCounter++;
                }
            }

            if(writtenObjectCounter == itemsPerFile){
                writtenObjectCounter = 0;
                fileCounter++;          
                tempList.clear();
            }
        }

        if(tempList.size() != 0){
        writeClosingRootElement();
        }

        return totalCounter;
    }

    private void writeToSplitFile(List<String> itemList) throws Exception{
        BufferedWriter wr = new BufferedWriter(new FileWriter(outputDirectory + File.separator + "split_" + fileCounter + ".xml", true));
        if(writtenObjectCounter == 0){
        wr.write("<" + rootElement + ">");
        wr.write("\n");
        }

        for (String string : itemList) {
            wr.write(string);
            wr.write("\n");
        }

        if(writtenObjectCounter == itemsPerFile-1)
        wr.write("</" + rootElement + ">");
        wr.close();
    }

    private void writeClosingRootElement() throws Exception{
        BufferedWriter wr = new BufferedWriter(new FileWriter(outputDirectory + File.separator + "split_" + fileCounter + ".xml", true));
        wr.write("</" + rootElement + ">");
        wr.close();
    }

    private boolean hasConditions(List<String> list){
        int matchList = 0;

        for (String condition : conditionList) {
            for (String string : list) {
                if(string.contains(condition)){
                    matchList++;
                }
            }
        }

        if(matchList >= conditionList.size()){
            return true;
        }

        return false;
    }

我知道每个写入的 staff 元素的打开/关闭流确实会影响性能。但是如果我每个文件写一次(其中可能包含 n 个staff)。自然地,根元素和拆分元素是可配置的。

有什么想法可以提高性能/逻辑吗?我更喜欢一些代码,但有时好的建议会更好

编辑:

这个 XML 示例实际上是一个虚拟示例,我尝试拆分的真正 XML 是拆分元素下大约 300-500 个不同的元素,它们都以随机顺序出现,并且数量各不相同。 Stax 可能不是最好的解决方案吗?

赏金更新:

我正在寻找一个解决方案(代码):

  • 能够将 XML 文件拆分为 n 个带有 x 个拆分元素的部分(来自虚拟 XML 示例人员是拆分元素)。

  • 被吐出的文件的内容应该被包裹在原始文件的根元素中(就像在虚拟示例公司中一样)

  • 我希望能够指定必须在拆分元素中的条件,即我只想要有昵称的员工,我想丢弃那些没有昵称的员工。但是在运行无条件拆分时也可以无条件拆分。

  • 代码不一定要改进我的解决方案(缺乏良好的逻辑和性能),但它可以工作。

并且对“但它有效”不满意。而且我找不到足够的 Stax 示例来进行此类操作,用户社区也不是很好。它也不必是 Stax 解决方案。

我可能要求太多了,但我来这里是为了学习东西,为我认为的解决方案提供丰厚的回报。

【问题讨论】:

  • 我将添加一个关于在 vtd-xml 中执行此操作的新条目...比目前建议的任何解决方案都要好

标签: java xml


【解决方案1】:

第一条建议:不要尝试编写自己的 XML 处理代码。使用 XML 解析器 - 它会更加更可靠,而且很可能更快。

如果您使用 XML 拉取解析器(例如 StAX),您应该能够一次读取一个元素并将其写入磁盘,而不是一次性读取整个文档。

【讨论】:

  • 感谢您的建议,但我现在真正想要的是一些代码示例.. 我读过太多类似的帖子(无意冒犯)
  • 我知道你是对的,我已经删除了代码,会再试一次。我想知道的一件事是人们为什么要投票给你的答案(我的意思是我以前用过谷歌)。谢谢
  • @Gandalf:可能是因为你想要建议,我给了你——他们同意这个建议。在几乎所有情况下,编写自己的 XML 处理代码确实是一个非常糟糕的主意。
  • 赞成答案,因为它需要说明:不要尝试自己操作 XML。始终使用 XML 解析器。
  • 这个答案有很多赞成票,因为它不是一个真正的答案(只是关于使用什么技术的警告和建议)。这个“答案”是我要发表评论的那种东西。可悲的是,正确答案只有一个赞成票。好吧,一秒两下。
【解决方案2】:

这是我的建议。它需要流式 XSLT 3.0 处理器:这意味着实际上它需要 Saxon-EE 9.3。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">

<xsl:mode streamable="yes">

<xsl:template match="/">
  <xsl:apply-templates select="company/staff"/>
</xsl:template>

<xsl:template match=staff">
  <xsl:variable name="v" as="element(staff)">
    <xsl:copy-of select="."/>
  </xsl:variable>
  <xsl:if test="$v/nickname">
    <xsl:result-document href="{@id}.xml">
      <xsl:copy-of select="$v"/>
    </xsl:result-document>
  </xsl:if>
</xsl:template>

</xsl:stylesheet>

不过,实际上,除非您有数百兆字节的数据,否则我怀疑非流式解决方案会足够快,而且可能比您的手写 Java 代码还要快,因为您的 Java 代码没什么可取的兴奋不已。无论如何,在编写大量低级 Java 之前先尝试一下 XSLT 解决方案。毕竟,这是一个常规问题。

【讨论】:

  • 文件只有几 GB,我的示例适用于所有文件,我只是想让它更快。
【解决方案3】:

您可以使用 StAX 执行以下操作:

算法

  1. 读取并按住根元素事件。
  2. 读取第一个 XML 块:
    1. 在满足条件之前排队事件。
    2. 如果条件已满足:
      1. 写入开始文档事件。
      2. 写出根开始元素事件
      3. 写出拆分起始元素事件
      4. 写出排队的事件
      5. 写出本节的剩余事件。
    3. 如果条件不满足,则什么也不做。
  3. 对下一个 XML 块重复第 2 步

用例代码

以下代码使用 StAX API 分解您问题中概述的文档:

package forum7408938;

import java.io.*;
import java.util.*;

import javax.xml.namespace.QName;
import javax.xml.stream.*;
import javax.xml.stream.events.*;

public class Demo {

    public static void main(String[] args) throws Exception  {
        Demo demo = new Demo();
        demo.split("src/forum7408938/input.xml", "nickname");
        //demo.split("src/forum7408938/input.xml", null);
    }

    private void split(String xmlResource, String condition) throws Exception {
        XMLEventFactory xef = XMLEventFactory.newFactory();
        XMLInputFactory xif = XMLInputFactory.newInstance();
        XMLEventReader xer = xif.createXMLEventReader(new FileReader(xmlResource));
        StartElement rootStartElement = xer.nextTag().asStartElement(); // Advance to statements element
        StartDocument startDocument = xef.createStartDocument();
        EndDocument endDocument = xef.createEndDocument();

        XMLOutputFactory xof = XMLOutputFactory.newFactory();
        while(xer.hasNext() && !xer.peek().isEndDocument()) {
            boolean metCondition;
            XMLEvent xmlEvent = xer.nextTag();
            if(!xmlEvent.isStartElement()) {
                break;
            }
            // BOUNTY CRITERIA
            // Be able to split XML file into n parts with x split elements(from
            // the dummy XML example staff is the split element).
            StartElement breakStartElement = xmlEvent.asStartElement();
            List<XMLEvent> cachedXMLEvents = new ArrayList<XMLEvent>();

            // BOUNTY CRITERIA
            // I'd like to be able to specify condition that must be in the 
            // split element i.e. I want only staff which have nickname, I want 
            // to discard those without nicknames. But be able to also split 
            // without conditions while running split without conditions.
            if(null == condition) {
                cachedXMLEvents.add(breakStartElement);
                metCondition = true;
            } else {
                cachedXMLEvents.add(breakStartElement);
                xmlEvent = xer.nextEvent();
                metCondition = false;
                while(!(xmlEvent.isEndElement() && xmlEvent.asEndElement().getName().equals(breakStartElement.getName()))) {
                    cachedXMLEvents.add(xmlEvent);
                    if(xmlEvent.isStartElement() && xmlEvent.asStartElement().getName().getLocalPart().equals(condition)) {
                        metCondition = true;
                        break;
                    }
                    xmlEvent = xer.nextEvent();
                }
            }

            if(metCondition) {
                // Create a file for the fragment, the name is derived from the value of the id attribute
                FileWriter fileWriter = null;
                fileWriter = new FileWriter("src/forum7408938/" + breakStartElement.getAttributeByName(new QName("id")).getValue() + ".xml");

                // A StAX XMLEventWriter will be used to write the XML fragment
                XMLEventWriter xew = xof.createXMLEventWriter(fileWriter);
                xew.add(startDocument);

                // BOUNTY CRITERIA
                // The content of the spitted files should be wrapped in the 
                // root element from the original file(like in the dummy example
                // company)
                xew.add(rootStartElement);

                // Write the XMLEvents that were cached while when we were
                // checking the fragment to see if it matched our criteria.
                for(XMLEvent cachedEvent : cachedXMLEvents) {
                    xew.add(cachedEvent);
                }

                // Write the XMLEvents that we still need to parse from this
                // fragment
                xmlEvent = xer.nextEvent();
                while(xer.hasNext() && !(xmlEvent.isEndElement() && xmlEvent.asEndElement().getName().equals(breakStartElement.getName()))) {
                    xew.add(xmlEvent);
                    xmlEvent = xer.nextEvent();
                }
                xew.add(xmlEvent);

                // Close everything we opened
                xew.add(xef.createEndElement(rootStartElement.getName(), null));
                xew.add(endDocument);
                fileWriter.close();
            }
        }
    }

}

【讨论】:

  • 这个拆分每个文件中只有 1 条记录。我也没有从原始文件中获得用根元素包装的拆分文件。我想我可以以某种方式做到这一点,但我想知道的是,您如何确保您的拆分元素包含某些 xml 子节点?
  • @Gandal StormCrow - 我已经更新了我的代码以符合你的赏金标准。
  • 这是一个很好的答案。另一种使 STAX 代码执行根元素包装的复杂性的替代方法是使用两阶段过程,一个是使用更简单的 STAX 将每个项目的子元素推送到内存中的 DOM 对象中,另一个是使用 XSLT转换 DOM 对象以添加包装元素。
【解决方案4】:

@Jon Skeet 在他的建议中像往常一样正确。 @Blaise Doughan 为您提供了使用 StAX 的非常基本的图片(这将是我的首选,尽管您可以使用 SAX 做基本相同的事情)。您似乎正在寻找更明确的东西,所以这里有一些伪代码可以帮助您入门(基于 StAX):

  1. 找到第一个“员工”StartElement
  2. 设置一个标志,表明您在“staff”元素中并开始跟踪深度(StartElement 为 +1,EndElement 为 -1)
  3. 现在,处理“员工”子元素,获取您关心的任何数据并将其放入文件(或任何地方)
  4. 继续处理直到深度达到 0(当您找到匹配的“staff”EndElement 时)
  5. 取消设置表明您在“员工”元素中的标志
  6. 搜索下一个“员工”StartElement
  7. 如果找到,请转到 2. 并重复
  8. 如果未找到,则文档完整

编辑:

哇,我不得不说我很惊讶有这么多人愿意为他们做别人的工作。我没有意识到 SO 基本上是一个免费版本的rent-a-coder。

【讨论】:

  • 我愿意为一个好的赏金做任何事,不是吗?实际上,我也愿意为 Twix 做任何事情;-)
  • @Wivani - twix,现在你在说话!但为了赏金,也许没那么多。
【解决方案5】:

@甘道夫风暴乌鸦: 让我将您的问题分为三个不同的问题:- i) 以最佳方式读取 XML 和同时拆分 XML

ii) 检查拆分文件中的条件

iii) 如果条件满足,则处理溢出的文件。

对于 i),当然有多种解决方案:SAX、STAX 和其他解析器,就像您提到的那样简单,只需使用简单的 java io 操作读取并搜索标签。

我相信 SAX/STAX/simple java IO,什么都行。我已将您的示例作为我的解决方案的基础。

ii) 检查拆分文件中的条件:您已使用 contains() 方法检查昵称是否存在。这似乎不是最好的方法:如果您的条件很复杂,好像应该存在昵称但长度>5 或薪水应该是数字等,该怎么办?

我会为此使用新的 Java XML 验证框架,它使用 XML 模式。请注意,我们可以将模式对象缓存在内存中,以便一次又一次地重用它。这个新的验证框架非常快。

iii) 如果条件满足,则处理该溢出文件。 您可能希望使用 java 并发 API 来提交异步任务(ExecutorService 类)以实现并行执行以获得更快的性能。

所以考虑到以上几点,一种可能的解决方案是:-

您可以创建一个 company.xsd 文件,如:-

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.example.org/NewXMLSchema"
    xmlns:tns="http://www.example.org/NewXMLSchema"
    elementFormDefault="unqualified">
    <element name="company">
        <complexType>
        <sequence>
            <element name="staff" type="tns:stafftype"/>
            </sequence>
        </complexType>

    </element>

    <complexType name="stafftype">
        <sequence>
        <element name="firstname" type="string" minOccurs="0" />
        <element name="lastname" type="string" minOccurs="0" />
        <element name="nickname" type="string" minOccurs="1" />
        <element name="salary" type="int" minOccurs="0" />
        </sequence>

    </complexType>

</schema>

那么您的 java 代码将如下所示:-

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.xml.sax.SAXException;

public class testXML {
    //  Lookup a factory for the W3C XML Schema language
    static SchemaFactory factory = SchemaFactory
            .newInstance("http://www.w3.org/2001/XMLSchema");

    //  Compile the schema. 
    static File schemaLocation = new File("company.xsd");
    static Schema schema = null;
    static {
        try {
            schema = factory.newSchema(schemaLocation);
        } catch (SAXException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private final ExecutorService pool = Executors.newFixedThreadPool(20);;

    boolean validate(StringBuffer splitBuffer) {
        boolean isValid = false;
        Validator validator = schema.newValidator();
        try {
            validator.validate(new StreamSource(new ByteArrayInputStream(
                    splitBuffer.toString().getBytes())));
            isValid = true;
        } catch (SAXException ex) {
            System.out.println(ex.getMessage());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return isValid;

    }

    void split(BufferedReader br, String rootElementName,
            String splitElementName) {
        StringBuffer splitBuffer = null;
        String line = null;
        String startRootElement = "<" + rootElementName + ">";
        String endRootElement = "</" + rootElementName + ">";

        String startSplitElement = "<" + splitElementName + ">";
        String endSplitElement = "</" + splitElementName + ">";
        String xmlDeclaration = "<?xml version=\"1.0\"";
        boolean startFlag = false, endflag = false;
        try {
            while ((line = br.readLine()) != null) {
                if (line.contains(xmlDeclaration)
                        || line.contains(startRootElement)
                        || line.contains(endRootElement)) {
                    continue;
                }

                if (line.contains(startSplitElement)) {
                    startFlag = true;
                    endflag = false;
                    splitBuffer = new StringBuffer(startRootElement);
                    splitBuffer.append(line);

                } else if (line.contains(endSplitElement)) {
                    endflag = true;
                    startFlag = false;
                    splitBuffer.append(line);
                    splitBuffer.append(endRootElement);

                } else if (startFlag) {
                    splitBuffer.append(line);
                }

                if (endflag) {
                    //process splitBuffer
                    boolean result = validate(splitBuffer);
                    if (result) {
                        //send it to a thread for processing further
                        //it is async so that main thread can continue for next

                        pool.submit(new ProcessingHandler(splitBuffer));

                    }
                }

            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

class ProcessingHandler implements Runnable {
    String splitXML = null;

    ProcessingHandler(StringBuffer splitXMLBuffer) {
        this.splitXML = splitXMLBuffer.toString();
    }

    @Override
    public void run() {
        // do like writing to a file etc.

    }

}

【讨论】:

    【解决方案6】:

    看看这个。这是来自 xmlpull.org 的稍微修改的示例:

    http://www.xmlpull.org/v1/download/unpacked/doc/quick_intro.html

    除非您有嵌套的拆分标签,否则以下内容应该可以满足您的所有需求:

    <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
    <company>
        <staff id="1">
            <firstname>yong</firstname>
            <lastname>mook kim</lastname>
            <nickname>mkyong</nickname>
            <salary>100000</salary>
            <other>
                <staff>
                ...
                </staff>
            </other>
        </staff>
    </company>
    

    要在传递模式下运行它,只需将 null 作为拆分标签传递。

    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    
    import org.apache.commons.io.FileUtils;
    import org.xmlpull.v1.XmlPullParser;
    import org.xmlpull.v1.XmlPullParserException;
    import org.xmlpull.v1.XmlPullParserFactory;
    
    public class XppSample {
    
    private String rootTag;
    private String splitTag;
    private String requiredTag;
    private int flushThreshold;
    private String fileName;
    
    private String rootTagEnd;
    
    private boolean hasRequiredTag = false;
    private int flushCount = 0;
    private int fileNo = 0;
    private String header;
    private XmlPullParser xpp;
    private StringBuilder nodeBuf = new StringBuilder();
    private StringBuilder fileBuf = new StringBuilder();
    
    
    public XppSample(String fileName, String rootTag, String splitTag, String requiredTag, int flushThreshold) throws XmlPullParserException, FileNotFoundException {
    
        this.rootTag = rootTag;
        rootTagEnd = "</" + rootTag + ">";
        this.splitTag = splitTag;
        this.requiredTag = requiredTag;
        this.flushThreshold = flushThreshold;
        this.fileName = fileName; 
    
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
        factory.setNamespaceAware(true);
        xpp = factory.newPullParser();
        xpp.setInput(new FileReader(fileName));
    }
    
    
    public void processDocument() throws XmlPullParserException, IOException {
        int eventType = xpp.getEventType();
        do {
            if(eventType == XmlPullParser.START_TAG) {
                processStartElement(xpp);
            } else if(eventType == XmlPullParser.END_TAG) {
                processEndElement(xpp);
            } else if(eventType == XmlPullParser.TEXT) {
                processText(xpp);
            }
            eventType = xpp.next();
        } while (eventType != XmlPullParser.END_DOCUMENT);
    
        saveFile();
    }
    
    
    public void processStartElement(XmlPullParser xpp) {
    
        int holderForStartAndLength[] = new int[2];
        String name = xpp.getName();
        char ch[] = xpp.getTextCharacters(holderForStartAndLength);
        int start = holderForStartAndLength[0];
        int length = holderForStartAndLength[1];
    
        if(name.equals(rootTag)) {
            int pos = start + length;
            header = new String(ch, 0, pos);
        } else {
            if(requiredTag==null || name.equals(requiredTag)) {
                hasRequiredTag = true;
            }
            nodeBuf.append(xpp.getText());
        }
    }
    
    
    public void flushBuffer() throws IOException {
        if(hasRequiredTag) {
            fileBuf.append(nodeBuf);
            if(((++flushCount)%flushThreshold)==0) {
                saveFile();
            }           
        }
        nodeBuf = new StringBuilder();
        hasRequiredTag = false;
    }
    
    
    public void saveFile() throws IOException {
        if(fileBuf.length()>0) {
            String splitFile = header + fileBuf.toString() + rootTagEnd;
            FileUtils.writeStringToFile(new File((fileNo++) + "_" + fileName), splitFile);
            fileBuf = new StringBuilder();
        }
    }
    
    
    public void processEndElement (XmlPullParser xpp) throws IOException {
    
        String name = xpp.getName();
    
        if(name.equals(rootTag)) {
            flushBuffer();
        } else {
            nodeBuf.append(xpp.getText());
            if(name.equals(splitTag)) {
                flushBuffer();
            }
        }
    }
    
    
    public void processText (XmlPullParser xpp) throws XmlPullParserException {
    
        int holderForStartAndLength[] = new int[2];
        char ch[] = xpp.getTextCharacters(holderForStartAndLength);
        int start = holderForStartAndLength[0];
        int length = holderForStartAndLength[1];
        String content = new String(ch, start, length);
    
        nodeBuf.append(content);
    }
    
    
    public static void main (String args[]) throws XmlPullParserException, IOException {
    
        //XppSample app = new XppSample("input.xml", "company", "staff", "nickname", 3);
        XppSample app = new XppSample("input.xml", "company", "staff", null, 3);
        app.processDocument();
    }
    

    }

    【讨论】:

    • 我的代码贡献大约是你的 1/10 :)
    【解决方案7】:

    通常我会建议使用 StAX,但我不清楚你的真实 XML 有多“有状态”。如果简单,则使用 SAX 以获得最佳性能,如果不那么简单,请使用 StAX。所以你需要

    1. 从磁盘读取字节
    2. 将它们转换为字符
    3. 解析 XML
    4. 确定是保留 XML 还是丢弃(跳过子树)
    5. 编写 XML
    6. 将字符转换为字节
    7. 写入磁盘

    现在,步骤 3-5 似乎是最耗费资源的,但我认为它们是

    最多:1 + 7
    中间:2 + 6
    最小:3 + 4 + 5

    由于操作 1 和 7 与其他操作是分开的,因此您应该以异步方式执行它们,如果您熟悉 multi-threading,至少创建多个小文件最好在 n 个其他线程中完成。为了提高性能,您还可以look into Java 中的新 IO 内容。

    现在,对于步骤 2 + 3 和 5 + 6,您可以使用 FasterXML,它确实可以完成很多您正在寻找的事情,例如在正确的位置触发 JVM 热点注意力;甚至可能支持快速浏览代码的异步读/写。

    那么我们只剩下第 5 步了,根据你的逻辑,你应该要么

    一个。做一个对象绑定,然后决定怎么做
    湾。无论如何都要写 XML,希望最好,如果没有 'staff' 元素,则将其丢弃。

    无论您做什么,对象重用都是明智的。请注意,这两种替代方案(显然)都需要相同数量的解析(尽快跳过子树),对于替代方案 b,一点额外的 XML 实际上在性能方面并没有那么糟糕,理想情况下,请确保您的 char 缓冲区大于一个单位。

    替代 b 是最容易实现的,只需将“xml 事件”从阅读器复制到写入器,例如 StAX:

    private static void copyEvent(int event, XMLStreamReader  reader, XMLStreamWriter writer) throws XMLStreamException {
        if (event == XMLStreamConstants.START_ELEMENT) {
            String localName = reader.getLocalName();
            String namespace = reader.getNamespaceURI();
            // TODO check this stuff again before setting in production
            if (namespace != null) {
                if (writer.getPrefix(namespace) != null) {
                    writer.writeStartElement(namespace, localName);
                } else {
                    writer.writeStartElement(reader.getPrefix(), localName, namespace);
                }
            } else {
                writer.writeStartElement(localName);
            }
            // first: namespace definition attributes
            if(reader.getNamespaceCount() > 0) {
                int namespaces = reader.getNamespaceCount();
    
                for(int i = 0; i < namespaces; i++) {
                    String namespaceURI = reader.getNamespaceURI(i);
    
                    if(writer.getPrefix(namespaceURI) == null) {
                        String namespacePrefix = reader.getNamespacePrefix(i);
    
                        if(namespacePrefix == null) {
                            writer.writeDefaultNamespace(namespaceURI);
                        } else {
                            writer.writeNamespace(namespacePrefix, namespaceURI);
                        }
                    }
                }
            }
            int attributes = reader.getAttributeCount();
    
            // the write the rest of the attributes
            for (int i = 0; i < attributes; i++) {
                String attributeNamespace = reader.getAttributeNamespace(i);
                if (attributeNamespace != null && attributeNamespace.length() != 0) {
                    writer.writeAttribute(attributeNamespace, reader.getAttributeLocalName(i), reader.getAttributeValue(i));
                } else {
                    writer.writeAttribute(reader.getAttributeLocalName(i), reader.getAttributeValue(i));
                }
            }
        } else if (event == XMLStreamConstants.END_ELEMENT) {
            writer.writeEndElement();
        } else if (event == XMLStreamConstants.CDATA) {
            String array = reader.getText();
            writer.writeCData(array);
        } else if (event == XMLStreamConstants.COMMENT) {
            String array = reader.getText();
            writer.writeComment(array);
        } else if (event == XMLStreamConstants.CHARACTERS) {
            String array = reader.getText();
            if (array.length() > 0 && !reader.isWhiteSpace()) {
                writer.writeCharacters(array);
            }
        } else if (event == XMLStreamConstants.START_DOCUMENT) {
            writer.writeStartDocument();
        } else if (event == XMLStreamConstants.END_DOCUMENT) {
            writer.writeEndDocument();
        }
    }
    

    对于子树,

    private static void copySubTree(XMLStreamReader reader, XMLStreamWriter writer) throws XMLStreamException {
        reader.require(XMLStreamConstants.START_ELEMENT, null, null);
    
        copyEvent(XMLStreamConstants.START_ELEMENT, reader, writer);
    
        int level = 1;
        do {
            int event = reader.next();
            if(event == XMLStreamConstants.START_ELEMENT) {
                level++;
            } else if(event == XMLStreamConstants.END_ELEMENT) {
                level--;
            }
    
            copyEvent(event, reader, writer);
        } while(level > 0);
    
    }
    

    从中您可能可以推断出如何跳出到某个级别。一般来说,对于有状态的 StaX 解析,使用模式

    private static void parseSubTree(XMLStreamReader reader) throws XMLStreamException {
    
        int level = 1;
        do {
            int event = reader.next();
            if(event == XMLStreamConstants.START_ELEMENT) {
                level++;
                // do stateful stuff here
    
                // for child logic:
                if(reader.getLocalName().equals("Whatever")) {
                    parseSubTreeForWhatever(reader);
                    level --; // read from level 1 to 0 in submethod.
                }
    
                // alternatively, faster
                if(level == 4) {
                    parseSubTreeForWhateverAtRelativeLevel4(reader);
                    level --; // read from level 1 to 0 in submethod.
                }
    
    
            } else if(event == XMLStreamConstants.END_ELEMENT) {
                level--;
                // do stateful stuff here, too
            }
    
        } while(level > 0);
    
    }
    

    您在文档开头的位置读取到第一个开始元素并中断(当然,添加作者+副本供您使用,如上所述)。

    请注意,如果您进行对象绑定,这些方法应该放在该对象中,对于序列化方法也是如此。

    我很确定您将在现代系统上获得 10 秒的 MB/s,这应该足够了。一个需要进一步研究的问题是使用多个内核进行实际输入的方法,如果您知道编码子集的事实,例如非疯狂的 UTF-8 或 ISO-8859,那么随机访问可能是可能的 -> 发送到不同的核心。

    玩得开心,并告诉使用它是怎么回事;)

    编辑:差点忘了,如果你是出于某种原因首先创建文件的人,或者你将在拆分后阅读它们,那么使用 XML 将获得巨大的性能提升二值化;存在可以再次进入代码生成器的 XML 模式生成器。 (并且一些 XSLT 转换库也使用代码生成。)并使用 JVM 的 -server 选项运行。

    【讨论】:

      【解决方案8】:

      如何让 i 更快:

      1. 如果您有 RAID-X 磁盘,则使用异步写入(可能是并行写入)可能会提高您的性能
      2. 写入 SSD 而不是 HDD

      【讨论】:

        【解决方案9】:

        我的建议是 SAX、STAX 或 DOM 不是解决您的问题的理想 xml 解析器,完美的解决方案称为 vtd-xml,有一篇关于此主题的文章解释了为什么 DOM sax 和 STAX 都做了一些非常错误的事情... 下面的代码是您必须编写的最短的代码,但执行速度比 DOM 或 SAX 快 10 倍。 http://www.javaworld.com/javaworld/jw-07-2006/jw-0724-vtdxml.html

        这是一篇题为Processing XML with Java – A Performance Benchmark的最新论文:http://recipp.ipp.pt/bitstream/10400.22/1847/1/ART_BrunoOliveira_2013.pdf

        import com.ximpleware.*;
        import java.io.*;
        public class gandalf {
            public  static void main(String a[]) throws VTDException, Exception{
                VTDGen vg = new VTDGen();
                if (vg.parseFile("c:\\xml\\gandalf.txt", false)){
                    VTDNav vn=vg.getNav();
                    AutoPilot ap = new AutoPilot(vn);
                    ap.selectXPath("/company/staff[nickname]");
                    int i=-1;
                    int count=0;
                    while((i=ap.evalXPath())!=-1){
                        vn.dumpFragment("c:\\xml\\staff"+count+".xml");
                        count++;
                    }
                }
            }
        
        }
        

        【讨论】:

          【解决方案10】:

          这是基于 DOM 的解决方案。我已经用您提供的 xml 对此进行了测试。这需要根据您拥有的实际 xml 文件进行检查。

          由于这是基于 DOM 解析器,请记住,这将需要 大量内存,具体取决于您的 xml 文件大小。但它的速度要快得多,因为它是基于 DOM 的。

          算法:

          1. 解析文档
          2. 提取根元素名称
          3. 根据拆分条件获取节点列表(使用 XPath)
          4. 为每个节点创建一个空文档,其根元素名称与步骤 #2 中提取的一样
          5. 在这个新文档中插入节点
          6. 检查是否要过滤节点。
          7. 如果要过滤节点,则检查新创建的文档中是否存在指定元素。
          8. 如果节点不存在,请不要写入文件。
          9. 如果根本不过滤节点,则不要检查 #7 中的条件,并将文档写入文件。

          这可以从命令提示符下运行,如下所示

          java    XMLSplitter xmlFileLocation  splitElement filter filterElement
          

          对于您提到的xml,它将是

          java    XMLSplitter input.xml  staff  true nickname
          

          如果你不想过滤

          java    XMLSplitter input.xml  staff 
          

          这里是完整的java代码:

          包 com.xml.xpath;

          import java.io.File;
          import java.io.FileWriter;
          import java.io.IOException;
          import java.io.StringReader;
          import java.io.StringWriter;
          
          import javax.xml.parsers.DocumentBuilder;
          import javax.xml.parsers.DocumentBuilderFactory;
          import javax.xml.parsers.ParserConfigurationException;
          import javax.xml.transform.OutputKeys;
          import javax.xml.transform.Transformer;
          import javax.xml.transform.TransformerConfigurationException;
          import javax.xml.transform.TransformerException;
          import javax.xml.transform.TransformerFactory;
          import javax.xml.transform.dom.DOMSource;
          import javax.xml.transform.stream.StreamResult;
          import javax.xml.xpath.XPath;
          import javax.xml.xpath.XPathConstants;
          import javax.xml.xpath.XPathExpression;
          import javax.xml.xpath.XPathExpressionException;
          import javax.xml.xpath.XPathFactory;
          
          import org.w3c.dom.DOMException;
          import org.w3c.dom.DOMImplementation;
          import org.w3c.dom.Document;
          import org.w3c.dom.Element;
          import org.w3c.dom.Node;
          import org.w3c.dom.NodeList;
          import org.xml.sax.InputSource;
          import org.xml.sax.SAXException;
          
          public class XMLSplitter {
          
              DocumentBuilder builder = null;
              XPath xpath = null; 
              Transformer transformer = null;
              String filterElement;
              String splitElement;
              String xmlFileLocation;
              boolean filter = true;
          
          
              public static void main(String[] arg) throws Exception{
          
                  XMLSplitter xMLSplitter = null;
                  if(arg.length < 4){
          
                      if(arg.length < 2){
                          System.out.println("Insufficient arguments !!!");
                          System.out.println("Usage: XMLSplitter xmlFileLocation  splitElement filter filterElement ");
                          return;
                      }else{
                          System.out.println("Filter is off...");
                          xMLSplitter = new XMLSplitter();
                          xMLSplitter.init(arg[0],arg[1],false,null);
                      }
          
                  }else{
                      xMLSplitter = new XMLSplitter();
                      xMLSplitter.init(arg[0],arg[1],Boolean.parseBoolean(arg[2]),arg[3]);
                  }
          
          
          
                  xMLSplitter.start();    
          
              }
          
              public void init(String xmlFileLocation, String splitElement, boolean filter, String filterElement ) 
                          throws ParserConfigurationException, TransformerConfigurationException{
          
                  //Initialize the Document builder
                  System.out.println("Initializing..");
                  DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
                  domFactory.setNamespaceAware(true);   
                  builder = domFactory.newDocumentBuilder();
          
                  //Initialize the transformer
                  TransformerFactory transformerFactory = TransformerFactory.newInstance();
                  transformer = transformerFactory.newTransformer();
                  transformer.setOutputProperty(OutputKeys.METHOD, "xml");
                  transformer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");
                  transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
                  transformer.setOutputProperty(OutputKeys.INDENT, "yes");
          
                  //Initialize the xpath
                  XPathFactory factory = XPathFactory.newInstance();
                  xpath = factory.newXPath();
          
                  this.filterElement = filterElement;
                  this.splitElement = splitElement;
                  this.xmlFileLocation = xmlFileLocation;
                  this.filter = filter;
          
          
              }   
          
          
              public void start() throws Exception{
          
                      //Parser the file 
                      System.out.println("Parsing file.");
                      Document doc = builder. parse(xmlFileLocation);
          
                      //Get the root node name
                      System.out.println("Getting root element.");
                      XPathExpression rootElementexpr = xpath.compile("/");
                      Object rootExprResult = rootElementexpr.evaluate(doc, XPathConstants.NODESET);
                      NodeList rootNode = (NodeList) rootExprResult;          
                      String rootNodeName = rootNode.item(0).getFirstChild().getNodeName();
          
                      //Get the list of split elements
                      XPathExpression expr = xpath.compile("//"+splitElement);
                      Object result = expr.evaluate(doc, XPathConstants.NODESET);
                      NodeList nodes = (NodeList) result;
                      System.out.println("Total number of split nodes "+nodes.getLength());
                      for (int i = 0; i < nodes.getLength(); i++) {
                          //Wrap each node inside root of the parent xml doc
                          Node sigleNode = wrappInRootElement(rootNodeName,nodes.item(i));
                          //Get the XML string of the fragment
                          String xmlFragment = serializeDocument(sigleNode);
                          //System.out.println(xmlFragment);
                          //Write the xml fragment in file.
                          storeInFile(xmlFragment,i);         
                      }
          
              }
          
              private  Node wrappInRootElement(String rootNodeName, Node fragmentDoc) 
                          throws XPathExpressionException, ParserConfigurationException, DOMException, 
                                  SAXException, IOException, TransformerException{
          
                  //Create empty doc with just root node
                  DOMImplementation domImplementation = builder.getDOMImplementation();
                  Document doc = domImplementation.createDocument(null,null,null);
                  Element theDoc = doc.createElement(rootNodeName);
                  doc.appendChild(theDoc);
          
                  //Insert the fragment inside the root node 
                  InputSource inStream = new InputSource();     
                  String xmlString = serializeDocument(fragmentDoc);
                  inStream.setCharacterStream(new StringReader(xmlString));       
                  Document fr = builder.parse(inStream);
                  theDoc.appendChild(doc.importNode(fr.getFirstChild(),true));
                  return doc;
              }
          
              private String serializeDocument(Node doc) throws TransformerException, XPathExpressionException{
          
                  if(!serializeThisNode(doc)){
                      return null;
                  }
          
                  DOMSource domSource = new DOMSource(doc);                
                  StringWriter stringWriter = new StringWriter();
                  StreamResult streamResult = new StreamResult(stringWriter);
                  transformer.transform(domSource, streamResult);
                  String xml = stringWriter.toString();
                  return xml;
          
              }
          
              //Check whether node is to be stored in file or rejected based on input
              private boolean serializeThisNode(Node doc) throws XPathExpressionException{
          
                   if(!filter){
                       return true;
                   }
          
                   XPathExpression filterElementexpr = xpath.compile("//"+filterElement);
                   Object result = filterElementexpr.evaluate(doc, XPathConstants.NODESET);
                   NodeList nodes = (NodeList) result;
          
                   if(nodes.item(0) != null){
                       return true;
                   }else{
                       return false;
                   }       
              }
          
              private void storeInFile(String content, int fileIndex) throws IOException{
          
                  if(content == null || content.length() == 0){
                      return;
                  }
          
                  String fileName = splitElement+fileIndex+".xml";
          
                  File file = new File(fileName);
                  if(file.exists()){
                      System.out.println(" The file "+fileName+" already exists !! cannot create the file with the same name ");
                      return;
                  }
                  FileWriter fileWriter = new FileWriter(file);
                  fileWriter.write(content);
                  fileWriter.close();
                  System.out.println("Generated file "+fileName);
          
          
              }
          
          }
          

          让我知道这是否适合您或有关此代码的任何其他帮助。

          【讨论】:

          • 与 SAX 解决方案相比,DOM 可能不会赢得性能价格。 OP 提到了相当大的数据文件。
          • 我同意这一点,这取决于其他各种因素是否一个或另一个更快。但是我被教导这种情况的方式需要一个 SAX 解决方案。我相信this article 为我更直观的偏好添加了一些论据。
          • 感谢分享此链接。确实支持你说的。此应用程序可能还需要在此处使用不同的建议解决方案进行此类基准测试。我相信 OP 应该这样做,并且最终会在这里分享。
          • 是的,投反对票,因为 DOM 不能提供良好的性能,并且常规服务器中没有足够的内存来将尽可能多的 XML 解析为 DOM 树,并且 XPath 对上述示例 XML 来说太过分了。因此,虽然是一种有效的方法,但问题在于提高性能。
          • 那篇文章是 2002 年的。不管有什么限制,如果由同样有能力的人实施,DOM 在过滤方面并不比 SAX 快。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-06-30
          • 2011-06-22
          • 1970-01-01
          • 2022-10-04
          相关资源
          最近更新 更多