[英]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文件大小,但考虑到您的问题,因为您想解析xml并写入平面文件,我猜想结合XML Pull Parsing和智能代码来写入平面文件( 这可能会有所帮助),因为我们不想耗尽 Java 堆。 您可以在 Google 上快速搜索有关使用 XML Pull Parsing 的教程和示例代码。
最后,我实现了一个自定义的 StaxEventItemReader。
配置片段RootElementName
配置我自己的 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>
在 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 工具,例如
如果您接受 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.