Alex Rivera | Logout

How to improve splitting xml file performance

Asked 2011-09-13T21:44:07.970
11

I've see quite a lot posts/blogs/articles about splitting XML file into a smaller chunks and decided to create my own because I have some custom requirements. Here is what I mean, consider the following 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>

I want to split this xml into n parts, each containing 1 file, but the staff element must contain nickname , if it's not there I don't want it. So this should produce 4 xml splits, each containing staff id starting at 1 until 4.

Here is my code :

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("<" 
Edit
Report

3 Answers

10

Here's my suggestion. It requires a streaming XSLT 3.0 processor: which means in practice that it needs 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>

In practice, though, unless you have hundreds of megabytes of data, I suspect a non-streaming solution will be quite fast enough, and probably faster than your hand-written Java code, given that your Java code is nothing to get excited about. At any rate, give an XSLT solution a try before you write reams of low-level Java. It's a routine problem, after all.

answered 2011-09-14T10:05:08.647
6

You could do the following with StAX:

Algorithm

  1. Read and hold onto the root element event.
  2. Read first chunk of XML:
    1. Queue events until condition has been met.
    2. If condition has been met:
      1. Write start document event.
      2. Write out root start element event
      3. Write out split start element event
      4. Write out queued events
      5. Write out remaining events for this section.
    3. If condition was not met then do nothing.
  3. Repeat step 2 with next chunk of XML

Code for Your Use Case

The following code uses StAX APIs to break up the document as outlined in your question:

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
  
answered 2011-09-14T13:28:47.857
1

Normally I would suggest using StAX, but it is unclear to me how 'stateful' your real XML is. If simple, then use SAX for ultimate performance, if not-so-simple, use StAX. So you need to

  1. read bytes from disk
  2. convert them to characters
  3. parse the XML
  4. determine whether to keep XML or throw away (skip out subtree)
  5. write XML
  6. convert characters to bytes
  7. write to disk

Now, it might seem like steps 3-5 are the most resource-intensive, but I would rate them as

Most: 1 + 7
Middle: 2 + 6
Least: 3 + 4 + 5

As operations 1 and 7 are kind of seperate of the rest, you should do them in an async way, at least creating multiple small files is best done in n other threads, if you are familiar with multi-threading. For increased performance, you might also look into the new IO stuff in Java.

Now for steps 2 + 3 and 5 + 6 you can go a long way with FasterXML, it really does a lot of the stuff you are looking for, like triggering JVM hot-spot attention in the right places; might even support async reading/writing looking through the code quickly.

So then we are left with step 5, and depending on your logic, you should either

a. make an object binding, then decide how what to do
b. write XML anyways, hoping for the best, and then throw it away if no 'staff' element is present.

Whatever you do, object reuse is sensible. Note that both alternatives (obisously) requires the same amount of parsing (skip out of subtree ASAP), and for alternative b, that a little extra XML is actually not so bad performancewise, ideally make sure your char buffers are

answered 2011-09-22T04:46:44.057

Your Answer