Java如何将Document对象输出到OutputStream

Java中将Document写入OutputStream应使用Transformer序列化:创建TransformerFactory和Transformer,设置输出属性(如编码、缩进、XML声明),用DOMSource和StreamResult包装后调用transform();需确保Transformer编码与OutputStream字节写入一致,避免乱码或异常。

Java中将Document对象(通常来自DOM解析)写入OutputStream,核心是使用Transformer进行序列化。关键在于正确配置Transformer,避免默认输出带XML声明、缩进或编码不一致等问题。

使用Transformer将Document写入OutputStream

这是最标准、推荐的方式,基于JAXP的XSLT处理器(如Xalan或内置的XSLTC):

  • 创建TransformerFactory并获取Transformer实例(可设为恒等转换)
  • DOMSource包装Document,用StreamResult包装目标OutputStream
  • 调用transform()执行序列化

示例代码:

TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
// 可选:设置输出属性(如去掉XML声明、指定编码、启用缩进)
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");

DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(outputStream);
transformer.transform(source, result);

注意OutputStream的编码与Transformer编码需一致

TransformerENCODING属性仅控制XML内容中的编码声明(如),实际写入字节流时,OutputStream本身不处理编码——它只写入字节。因此:

  • outputStreamFileOutputStream,直接写入即可;编码由Transformer负责转换
  • outputStreamByteArrayOutputStream或网络流,也无需额外包装
  • 切勿将OutputStream包装成OutputStreamWriter再传给StreamResult——StreamResult只接受原始OutputStreamWriter,混用会导致乱码或异常

常见问题与规避方式

实际使用中容易遇到几个典型问题:

  • 中文乱码:确保OutputKeys.ENCODING设为"UTF-8",且XML文档本身节点文本已正确编码(DOM内部以Unicode存储,一般无问题)
  • 多出空白或换行:关闭INDENT,或设OutputKeys.INDENT"no";也可用transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "0")更精确控制
  • DOCTYPE丢失:默认Transformer不会输出DOCTYPE;如需保留,需在创建Document时确保DocumentType存在,并确认JDK版本支持(较新版本通常保留)

替代方案:使用javax.xml.parsers.DocumentBuilder + StringWriter(不推荐)

有人尝试先写入StringWriter再转字节,但这是低效且易出错的做法:

  • 会把整个XML加载为字符串,内存开销大
  • 手动转字节时易忽略编码一致性(如string.getBytes("UTF-8") vs Transformer内置编码)
  • 无法流式处理,丧失OutputStream的灵活性(如写入Socket、Servlet响应)

除非特殊限制,应始终优先使用Transformer + StreamResult(OutputStream)路径。