将大字符串流式传输到 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。覆盖writeStartElementwriteCharacters 以拦截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 &amp;&lt;&gt;'\"</FirstField>"
                          + "<Second>&lt;!CDATA[[ &lt;!-- now we're just nasty --&gt; ]]&gt;</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

将大文件发送到 BigQuery

将字符串流式传输到 pygments 词法分析器?

使用promises将多个svg字符串流式传输到nodejs中的png

ESP8266 WebServer如何流式传输无符号字符

使用 Json.net 将大量数据流式传输为 JSON 格式