格式化输入流的正确方法

时间:2011-01-03 16:28:11

标签: java formatting inputstream super

我有以下问题:我的程序传递了一个InputStream,我无法控制其内容。我使用javax库解组我的输入流,如果InputStream包含&,则会正确地抛出异常。字符后面没有“amp;”

我想出的解决方法是创建以下类:

import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.InputStream;

/**
 * Provide an input stream where all & characters are properly encoded as &
 */
public class FormattedStream extends FilterInputStream {
  public FormattedStream(InputStream src) {
    super(new ByteArrayInputStream(StringUtil.toString(src)
      .replace("&", "&").replace("amp;amp;", "amp;").getBytes()));
  }
}

注意:StringUtil是一个简单的实用程序,我必须将输入流转换为String。

有了这个类,我现在用:

调用JAXB unmarshaller
unmarshal(new FormattedStream(inputStream));

而不是

unmarshal(inputStream);

这种方法有效,但由于以下几个原因看起来很奇怪:

1 - 由于super必须是构造函数中的第一个元素的限制(尽管我读到了它,但我无法理解限制),我被迫在一行中完成所有处理,使代码远来自可读。

2 - 将整个流转换为字符串并返回到流似乎过度

3 - 上面的代码略微不正确,因为包含amp; amp; amp;将被修改为包含amp;

我可以通过一个方法提供一个FormatInputStream类来解决1:

InputStream preProcess(InputStream inputStream)

我将在FormattedStream类的构造函数中执行相同的操作,但由于编码限制,必须选择不同的接口似乎很奇怪。

我可以通过保持FormattedStream构造函数简单来解决2:

super(src)

并覆盖三种读取方法,但这将涉及更多编码:通过替换& amp;来覆盖三种读取方法。与我目前拥有的可以利用replaceAll String方法的一行代码相比,动态并不是微不足道的。

至于3,看起来我不担心它的角落情况,但也许我应该......

有关如何以更优雅的方式解决我的问题的任何建议吗?

2 个答案:

答案 0 :(得分:3)

我同意McDowell的回答,最重要的是首先修复无效数据源

无论如何,这是一个InputStream,它会查找孤独的&个字符,如果缺少amp;,则会与其他&结婚。同样,以这种方式修复损坏的数据并不能在大部分时间内得到回报。

此解决方案修复了OP中提到的三个缺陷,并且只显示了实现转换InputStream的一种方法。

  • 在构造函数中,仅保留对原始InputStream的引用。 构造函数中不进行任何处理,直到确实要求数据流(通过调用read())。
  • 内容未转换为大型单个字符串进行转换。相反,流作为流工作,只执行最小的预读(例如,找出amp;是否后跟&所需的四个字节。
  • 该流只会替换孤独的amp;amp;不会以任何方式尝试清除import java.io.IOException; import java.io.InputStream; import java.util.ArrayDeque; import java.util.Deque; public class ReplacerInputStream extends InputStream { private static final byte[] REPLACEMENT = "amp;".getBytes(); private final byte[] readBuf = new byte[REPLACEMENT.length]; private final Deque<Byte> backBuf = new ArrayDeque<Byte>(); private final InputStream in; public ReplacerInputStream(InputStream in) { this.in = in; } @Override public int read() throws IOException { if (!backBuf.isEmpty()) { return backBuf.pop(); } int first = in.read(); if (first == '&') { peekAndReplace(); } return first; } private void peekAndReplace() throws IOException { int read = super.read(readBuf, 0, REPLACEMENT.length); for (int i1 = read - 1; i1 >= 0; i1--) { backBuf.push(readBuf[i1]); } for (int i = 0; i < REPLACEMENT.length; i++) { if (read != REPLACEMENT.length || readBuf[i] != REPLACEMENT[i]) { for (int j = REPLACEMENT.length - 1; j >= 0; j--) { // In reverse order backBuf.push(REPLACEMENT[j]); } return; } } } } ,因为这种解决方案不会发生这种情况。

    test("Foo &amp; Bar", "Foo & Bar");
    test("&amp;&amp;&amp;", "&&&");
    test("&amp;&amp;&amp; ", "&&& ");
    test(" &amp;&amp;&amp;", " &&&");
    test("&amp;", "&");
    test("&amp;", "&amp;");
    test("&amp;&amp;", "&amp;&amp;");
    test("&amp;&amp;&amp;", "&amp;&&amp;");
    test("test", "test");
    test("", "");
    test("testtesttest&amp;", "testtesttest&");

已使用以下输入数据测试代码(第一个参数是预期输出,第二个参数是原始输入):

{{1}}

答案 1 :(得分:0)

为了避免将所有数据读入RAM,您可以实现FilterInputStream(您必须覆盖read()read(byte[],int,int)并查看以某种方式缓冲这些额外字节。这将不会导致更短的代码。


真正的解决方案是修复无效的数据源(如果您要自动执行该操作,则需要编写自己的XML解析器)。

你的方法有一些缺陷。

  • String.getBytes()的结果取决于系统;它也是一种代码转换操作,可能与StringUtil.toString所做的任何事情都不对称 - 许多系统上的默认编码都是lossy。您应该使用XML document encoding
  • 执行转码
  • 像这样的全局搜索和替换可能会损坏您的文档 - &CDATAentities and entity declarations中可能存在&符号。