如何解析大型复杂 XML



我需要解析一个大型的复杂xml并写入一个平面文件,你能给出一些建议吗?

文件大小: 500MB记录数:100KXML结构:

<Msg>
    <MsgHeader>
        <!--Some of the fields in the MsgHeader need to be map to a java object-->
    </MsgHeader>
    <GroupA> 
        <GroupAHeader/>
        <!--Some of the fields in the GroupAHeader need to be map to a java object--> 
        <GroupAMsg/>
        <!--50K records--> 
        <GroupAMsg/> 
        <GroupAMsg/> 
        <GroupAMsg/> 
    </GroupA>
    <GroupB> 
        <GroupBHeader/> 
        <GroupBMsg/>
        <!--50K records--> 
        <GroupBMsg/> 
        <GroupBMsg/> 
        <GroupBMsg/> 
    </GroupB>
</Msg>

在Spring Batch中,我编写了自己的stax事件项读取器实现,其操作比前面提到的更具体一些。基本上,我只是将元素填充到映射中,然后将它们传递到ItemProcessor中。从那里,你可以自由地从"GatheredElement"将其转换为单个对象(参见CompositeItemProcessor)。很抱歉从StaxEventItemReader复制/粘贴了一些内容,但我认为这是无法避免的。

从这里,你可以自由使用任何你想要的OXM编组器,我碰巧也使用JAXB。

public class ElementGatheringStaxEventItemReader<T> extends StaxEventItemReader<T> {
    private Map<String, String> gatheredElements;
    private Set<String> elementsToGather;
    ...
    @Override
    protected boolean moveCursorToNextFragment(XMLEventReader reader) throws NonTransientResourceException {
        try { 
            while (true) {
                while (reader.peek() != null && !reader.peek().isStartElement()) {
                    reader.nextEvent();
                }
                if (reader.peek() == null) {
                    return false;
                }
                QName startElementName = ((StartElement) reader.peek()).getName();
                if(elementsToGather.contains(startElementName.getLocalPart())) {
                    reader.nextEvent(); // move past the actual start element
                    XMLEvent dataEvent = reader.nextEvent();
                    gatheredElements.put(startElementName.getLocalPart(), dataEvent.asCharacters().getData());
                    continue;
                }
                if (startElementName.getLocalPart().equals(fragmentRootElementName)) {
                    if (fragmentRootElementNameSpace == null || startElementName.getNamespaceURI().equals(fragmentRootElementNameSpace)) {
                        return true;
                    }
                }
                reader.nextEvent();
            }
        } catch (XMLStreamException e) {
            throw new NonTransientResourceException("Error while reading from event reader", e);
        }
    }
    @SuppressWarnings("unchecked")
    @Override
    protected T doRead() throws Exception {
        T item = super.doRead();
        if(null == item)
            return null;
        T result = (T) new GatheredElementItem<T>(item, new     HashedMap(gatheredElements));
        if(log.isDebugEnabled())
            log.debug("Read GatheredElementItem: " + result);
        return result; 
    }

收集的元素类非常基本:

public class GatheredElementItem<T> {
    private final T item;
    private final Map<String, String> gatheredElements;
    ...
}

我没有处理过如此巨大的 xml 文件大小,但考虑到您的问题,由于您想解析 xml 并写入平面文件,我猜 XML 拉取解析和智能代码的组合来写入平面文件(这可能会有所帮助),因为我们不想耗尽 Java 堆。您可以在 Google 上快速搜索有关使用 XML 拉取分析的教程和示例代码。

最后,我实现了一个定制的StaxEventItemReader。

  1. 配置片段根元素名称

  2. 配置我自己的手册手柄元素

    <property name="manualHandleElement">
    <list>
        <map>
            <entry>
                <key><value>startElementName</value></key>
                <value>GroupA</value>
            </entry>
            <entry>
                <key><value>endElementName</value></key>
                <value>GroupAHeader</value>
            </entry>
            <entry>
                <key><value>elementNameList</value></key>
                    <list>
                            <value>/GroupAHeader/Info1</value>
                            <value>/GroupAHeader/Info2</value>
                    </list>
            </entry>
        </map>
    </list>
    

  3. 在 MyStaxEventItemReader.doRead() 中添加以下片段

    while(true){
    if(reader.peek() != null && reader.peek().isStartElement()){
        pathList.add("/"+((StartElement) reader.peek()).getName().getLocalPart());
        reader.nextEvent();
        continue;
    }
    if(reader.peek() != null && reader.peek().isEndElement()){
        pathList.remove("/"+((EndElement) reader.peek()).getName().getLocalPart());
        if(isManualHandleEndElement(((EndElement) reader.peek()).getName().getLocalPart())){
            pathList.clear();
            reader.nextEvent();
            break;
        }
        reader.nextEvent();
        continue;
    }
    if(reader.peek() != null && reader.peek().isCharacters()){
        CharacterEvent charEvent = (CharacterEvent)reader.nextEvent();
        String currentPath = getCurrentPath(pathList);
        String startElementName = (String)currentManualHandleStartElement.get(MANUAL_HANDLE_START_ELEMENT_NAME);
        for(Object s : (List)currentManualHandleStartElement.get(MANUAL_HANDLE_ELEMENT_NAME_LIST)){
            if(("/"+startElementName+s).equals(currentPath)){
                map.put(getCurrentPath(pathList), charEvent.getData());
                break;
            }
        }
        continue;
    }
    reader.nextEvent();
    

    }

尝试一些ETL工具,例如

Pentaho Data Integration (AKA Kettle)

如果您接受 JAXB/Spring Batch 以外的解决方案,您可能需要查看 SAX 解析器。

这是一种更面向事件的分析 XML 文件的方法,当您希望在分析时直接写入目标文件时,这可能是一个好方法。SAX 分析器不会将整个 xml 内容读入内存,而是在输入流中的元素时触发方法。据我所知,这是一种非常节省内存的处理方式。

与Stax-Solution相比,SAX将数据"推送"到您的应用程序中 - 这意味着您必须维护状态(例如您位于哪个标签中),因此您必须跟踪当前位置。我不确定这是否是你真正需要的东西

下面的示例读取结构中的 xml 文件,并打印出 GroupBMsg 标记中的所有文本:

import java.io.FileReader;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
public class SaxExample implements ContentHandler
{
    private String currentValue;
    public static void main(final String[] args) throws Exception
    {
        final XMLReader xmlReader = XMLReaderFactory.createXMLReader();
        final FileReader reader = new FileReader("datasource.xml");
        final InputSource inputSource = new InputSource(reader);
        xmlReader.setContentHandler(new SaxExample());
        xmlReader.parse(inputSource);
    }
    @Override
    public void characters(final char[] ch, final int start, final int length) throws     SAXException
    {
        currentValue = new String(ch, start, length);
    }
    @Override
    public void startElement(final String uri, final String localName, final String     qName, final Attributes atts) throws SAXException
    {
        // react on the beginning of tag "GroupBMsg" <GroupBMSg>
        if (localName.equals("GroupBMsg"))
        {
            currentValue="";
        }
    }
    @Override
    public void endElement(final String uri, final String localName, final String     qName) throws SAXException
    {
        // react on the ending of tag "GroupBMsg" </GroupBMSg>
        if (localName.equals("GroupBMsg"))
        {
            // TODO: write into file
            System.out.println(currentValue);
        }
    }

    // the rest is boilerplate code for sax
    @Override
    public void endDocument() throws SAXException {}
    @Override
    public void endPrefixMapping(final String prefix) throws SAXException {}
    @Override
    public void ignorableWhitespace(final char[] ch, final int start, final int length)
        throws SAXException {}
    @Override
    public void processingInstruction(final String target, final String data)
        throws SAXException {}
    @Override
    public void setDocumentLocator(final Locator locator) {  }
    @Override
    public void skippedEntity(final String name) throws SAXException {}
    @Override
    public void startDocument() throws SAXException {}
    @Override
    public void startPrefixMapping(final String prefix, final String uri)
      throws SAXException {}
}

您可以使用声明性流映射 (DSM) 流分析库。它可以处理JSON和XML。它不会将 XML 文件加载到内存中。DSM 仅处理您在 YAML 或 JSON 配置中定义的数据。

可以在读取 XML 时调用方法。这允许您部分处理 XML。您可以将此部分读取的 XML 数据反序列化为 Java 对象。

甚至您可以使用它来读取多个线程。

你可以在这个答案中找到很好的例子

使用 STAX 解析器将 XML 解组为三个不同对象的列表

JAVA - 解析大型(超大)JSON文件的最佳方法(XML相同)