将大字符串流式传输到 JAXB
Posted
技术标签:
【中文标题】将大字符串流式传输到 JAXB【英文标题】:stream a large String into JAXB 【发布时间】:2013-11-18 17:25:15 【问题描述】:我的 JAXB 层次结构中有一个域对象,它必须表示为逗号分隔值文本。不幸的是,显式构建 CSV String
的成本非常高,所以这不是一种选择。
我创建了一个自定义的@XmlJavaTypeAdapter
,它返回了一个DataHandler
(根据supported data types),但它总是在BASE64中写出数据......但我有一个遗留的API来保存它,它需要ASCII字符串.更改 DataHandler
的 MIME 不会更改编码,但会影响 XSD 对其中包含的对象的定义。
有什么方法可以设置 DataHandler
(或任何其他受支持的 Java 类型)以从流输入返回未编码的 String
?
我也考虑过返回一个Object
(实际上是一个CharacterData
),但这需要实现public String getData()
...要求我明确地构造我正在尝试流式传输的String
。
【问题讨论】:
【参考方案1】:如果没有人提出与DataHanler
相关的解决方案...以下只是不涉及DataHandler
的“变通办法”的另一种想法。它需要访问 marshaller。
修改您的 XML 类型适配器,使其不返回内容,而是返回一种短地址以获取流数据(例如文件名)。
定义一个 XMLStreamWriter 包装器,如下所示:JAXB marshalling XMPP stanzas。覆盖writeStartElement
和writeCharacters
以拦截CSV 元素的startElement
调用和紧随其后的writeCharacters
。
传递给writeCharacters
的特定调用的数据将是获取流数据的地址。将其分块流式传输到包装的 XMLStreamWriter 的 writeCharacters。
【讨论】:
这看起来可行!我会试一试,如果有的话(可能一周)回复。【参考方案2】:我不太明白为什么显式构造 CSV 字符串(使用 StringBuilder)会比使用 JAXB 内置函数更昂贵。
如果性能是您的限制因素,那么我认为您应该考虑创建自定义序列化程序(例如基于 StringBuilder)和 SAX 处理程序来解析 XML。
如果您有幸更改协议,那么您可能想查看Grizzly framework、Avro 和Google ProtoBuf - 它们需要更多维护,但如果您追求性能,那么这些应该更快。
与往常一样,您应该使用这两种方法进行 A/B 性能测试,然后再将任何东西固定下来;)
回到原来的话题,下面是一个关于如何使用自定义适配器的例子:
import static org.junit.Assert.assertEquals;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.junit.Test;
public class Example
public String serialize( DataObject d ) throws JAXBException
StringWriter buffer = new StringWriter();
JAXBContext.newInstance(DataObject.class).createMarshaller().marshal(d, buffer);
return buffer.toString();
@Test
public void testSerialize( ) throws JAXBException
String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><dataObject>"
+ "<FirstField>field1 content with special characters &<>'\"</FirstField>"
+ "<Second><!CDATA[[ <!-- now we're just nasty --> ]]></Second>"
+ "<Custom>a,b,c</Custom></dataObject>";
assertEquals(expected, serialize(new DataObject()).replaceAll("(\r)?\n(\r)?", "\n"));
@XmlRootElement
@XmlAccessorType( XmlAccessType.FIELD )
class DataObject
@XmlElement( name = "FirstField" )
private final String field1 = "field1 content with special characters &<>'\"";
@XmlElement( name = "Second" )
private final String field2 = "<!CDATA[[ <!-- now we're just nasty --> ]]>";
@XmlElement( name = "Custom" )
@XmlJavaTypeAdapter( value = CustomAdapter.class )
// you can move this over the type
private final CustomType type = new CustomType("a", "b", "c");
@XmlAccessorType( XmlAccessType.FIELD )
class CustomType
private final String a;
private final String b;
private final String c;
public CustomType( String a, String b, String c )
this.a = a;
this.b = b;
this.c = c;
public String getA( )
return a;
public String getB( )
return b;
public String getC( )
return c;
class CustomAdapter extends XmlAdapter<String, CustomType>
@Override
public String marshal( CustomType v ) throws Exception
return String.format("%s,%s,%s", v.getA(), v.getB(), v.getC());
@Override
/** Please don't use this in PROD :> */
public CustomType unmarshal( String v ) throws Exception
String[] split = v.split(",");
return new CustomType(split[ 0 ], split[ 1 ], split[ 2 ]);
这应该会让你继续前进,除非我完全误解了你的问题。
【讨论】:
buffer.toString()
=> OutOfMemoryError
。简而言之,这就是问题所在 :-) (实际上,我们并没有 OOM,但想象一下同时处理大量这些请求,并想象一下 GC 在这种负载下如何应对。我们已经对其进行了分析,这是一个杀手,流媒体是解决方案。)
哦,这就是问题所在。我想您可以尝试在编组时使用输出流而不是字符串编写器,但这也可能会爆炸,具体取决于实现细节。另一件事是切换到完全流式 API - 这里有一些示例java.dzone.com/articles/xml-unmarshalling-benchmark(“JAXB + STax”部分),但如果 CSV 字符串本身就是问题,那么恐怕你遇到了一个极端情况。无论如何,如果我发现一些有趣的东西,我会在这里发布。
确切地说,我正在尝试将可流式对象传递给 JAXB(即从较小的对象动态构建 CSV)。我遇到的问题是 JAXB 中唯一可流式传输的对象流入 base64 并且我需要 ASCII。上面提供自定义拦截 XMLStreamWriter 的建议听起来不错,但很想听听您可能拥有的任何其他方法。以上是关于将大字符串流式传输到 JAXB的主要内容,如果未能解决你的问题,请参考以下文章
使用 Protobuf-net 将大数据文件流式传输为 IEnumerable