5

我需要解析一个大型复杂的 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>
4

6 回答 6

1

在 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;
    ...
}
于 2013-01-04T20:32:15.823 回答
0

我还没有处理过这么大的文件大小,但考虑到你的问题,因为你想解析并写入一个平面文件,我猜想结合XML Pull Parsing和智能代码来写入平面文件(这可能会有所帮助),因为我们不想耗尽 Java 堆。您可以在 Google 上快速搜索有关使用 XML Pull Parsing 的教程和示例代码。

于 2012-12-19T12:23:38.453 回答
0

如果您接受 JAXB/Spring Batch 之外的解决方案,您可能想看看 SAX Parser。

这是一种更加面向事件的解析 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 {}
}
于 2013-01-30T13:15:32.393 回答
0

最后,我实现了一个自定义的 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();
    

    }

于 2012-12-30T13:34:41.760 回答
0

尝试一些 ETL 工具,例如

Pentaho 数据集成 (AKA Kettle)

于 2012-12-30T13:45:48.750 回答
0

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

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

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

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

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

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

于 2020-04-09T11:01:14.670 回答