繁体   English   中英

如何解析大型复杂xml

[英]how to parse large complex xml

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

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

<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 Pull Parsing和智能代码来写入平面文件( 这可能会有所帮助),因为我们不想耗尽 Java 堆。 您可以在 Google 上快速搜索有关使用 XML Pull Parsing 的教程和示例代码。

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

  1. 配置片段RootElementName

  2. 配置我自己的 manualHandleElement

     <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 数据集成(又名 Kettle)

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

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

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

以下示例读入您结构中的 xml 文件并打印出 GroupBMsg-Tags 中的所有文本:

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 Parser 将 XML 解组为三个不同对象的列表

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM