我需要解析一个大型的复杂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。
-
配置片段根元素名称
-
配置我自己的手册手柄元素
<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工具,例如
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相同)