假设以下情况:

我们从某个库中获取了一个Java类,该类消耗了字节流,例如一些XML解析器XmlParser1,它公开了xmlParser1.parse(inputStream)方法;通常,该方法在一次调用中吃掉所有字节,最终阻塞。
我们还有另一个类库,该类库来自另一个库,具有类似的功能,但实现不同:XmlParser2xmlParser2.parse(inputStream)
现在,我们想用两个解析器解析单个流

我的第一个答案是:我们搞砸了。由于我们无法控制每个类如何使用流,因此我们所能做的就是将内存中的所有字节缓冲到一个临时文件中(或打开/重新打开它,如果可能的话)。这些消费者的API本质上是非合作的。

现在,假设我们已经控制了XmlParser1(实现和签名),并且希望以一种更加灵活和协作的方式对其进行编码,以便调用者可以以某种合理而有效的方式来实现上述行为...您会提出什么建议?

我正在考虑的一些替代方案:

1)使XmlParser1实现FilterInputStream,以便当某个类(XmlParser1)尝试从中读取某些字节时,它会在内部解析它必须执行的操作(迭代,也许有一些合理的缓冲)并还返回原始字节。 (我会说这与FilterInputStream概念不完全对应)。这样,客户端代码可以简单地链接解析器:

   public class XmlParser1 extends FilterInputStream {
       public XmlParser1(InputStream rawInputStream) { ... }
       public int read(byte[] b, int off, int l) throws IOException {
           // this would invoke the underlying stream read, parse internall the read bytes,
           // and leave them in the buffer
       }
   }

   XmlParser1 parser1 = new XmlParser1(inputstream);
   XmlParser2 parser2 = new XmlParser2(parse);
   parser2.parse(); // parser2 consumes all the input stream, which causes parser1 to read an parse it too

2)而不是将XmlParser1视为字节的使用者,而是将其视为接收器:我们不会让它自己吃掉字节,我们会用勺子喂它。因此,我们可以不用xmlParser1.parse(inputStream)xmlParser1.write(byte[]) ...,而不是将其传递给InputStream,而不是将其传递给OutputStream。这将允许客户端创建一个TeeInputStream,该字节将字节透明地传递到XmlParser2类,并同时调用XmlParser1.write()
请注意,在任何情况下我们都不需要单独的线程。

我不确定哪一个(如果有的话)在概念上更可取,是否有更好的选择。在我看来,这是一个应该已经讨论过的设计问题,但是我没有发现太多-不一定限于Java。欢迎意见和引用。

最佳答案

假设两个解析器在两个单独的线程中运行,则可能是这样的(不是工作代码)

public class Test extends FilterInputStream {
    byte[] buf = new byte[8192];
    int len;
    Thread thread = null;

    @Override
    public synchronized int read(byte[] b, int off, int l) throws IOException {
        while (thread == Thread.currentThread() && len > 0) {
            thread.wait();
        }
        if (len > 0) {
            System.arraycopy(buf, 0, b, off, l);
            len = 0;
            return l;
        }
        len = super.read(b, off, l);
        System.arraycopy(b, off, buf, 0, len);
        thread = Thread.currentThread();
        notify();
        return len;
    }

即#1读取字节并将其保存在buf中,#1的下一次尝试被阻止,直到#2从缓冲区读取所有字节

10-05 23:03