如何同时向两个java.io.OutputStream对象写入数据?

43

我正在寻找一个神奇的Java类,它能让我像这样做:

ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
FileOutputStream fileStream = new FileOutputStream(new File("/tmp/somefile"));

MultiOutputStream outStream = new MultiOutputStream(byteStream, fileStream);

outStream.write("Hello world".getBytes());

基本上,我想在Java中使用tee来处理OutputStream。 有什么想法吗?

谢谢!

5个回答

47
尝试使用Apache Commons TeeOutputStream。请参阅文档

27

自己写就可以了,没有什么魔法。使用Apache的TeeOutputStream,你只需要使用下面的代码。当然,使用Apache Commons I/O库可以利用其他类,但有时为自己编写代码也是不错的选择。 :)

public final class TeeOutputStream extends OutputStream {

  private final OutputStream out;
  private final OutputStream tee;

  public TeeOutputStream(OutputStream out, OutputStream tee) {
    if (out == null)
      throw new NullPointerException();
    else if (tee == null)
      throw new NullPointerException();

    this.out = out;
    this.tee = tee;
  }

  @Override
  public void write(int b) throws IOException {
    out.write(b);
    tee.write(b);
  }

  @Override
  public void write(byte[] b) throws IOException {
    out.write(b);
    tee.write(b);
  }

  @Override
  public void write(byte[] b, int off, int len) throws IOException {
    out.write(b, off, len);
    tee.write(b, off, len);
  }

  @Override
  public void flush() throws IOException {
    out.flush();
    tee.flush();
  }

  @Override
  public void close() throws IOException {
    try {
      out.close();
    } finally {
      tee.close();
    }
  }
}

使用上述类进行以下测试:

public static void main(String[] args) throws IOException {
  TeeOutputStream out = new TeeOutputStream(System.out, System.out);
  out.write("Hello world!".getBytes());
  out.flush();
  out.close();
}

将会打印出Hello World!Hello World!

(注意:虽然close()方法被重写了,但还需要进行一些调整:)


写操作应该同步。 - Dave Newton
1
你可以简化它。让它扩展FilterOutputStream;用super(out)构造它;去掉out成员;并将所有的out.引用更改为super.引用。 - user207421
@Dave 取决于使用情况。EJP 我更喜欢这种方式,但那种方式也可以。 - Kohányi Róbert
@DaveNewton,能否解释一下你关于“写操作应该同步”的评论,这是必须的吗?常规的Java IO流是否使用synchronized关键字? - Whome
1
@KohányiRóbert,有一些神奇的事情。使用许多其他人正在使用的代码可以增加发现和修复代码中任何错误的可能性。例如,当out.close()抛出一个IOException并且tee.close()被跳过时,因为异常处理没有确保第二个流被关闭。 - neuralmer
显示剩余2条评论

4
final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
final FileOutputStream fileStream = new FileOutputStream(new File("/tmp/somefile"));
OutputStream outStream = new OutputStream() {

    public void write(int b) throws IOException {
        byteStream.write(b);
        fileStream.write(b);
    }
};
outStream.write("Hello world".getBytes());

4

我发现了这个帖子,因为我也面临着同样的问题。 如果有人想看我的解决方案(Java7代码):

package Core;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class MultiOutputStream extends OutputStream {

private List<OutputStream> out;

public MultiOutputStream(List<OutputStream> outStreams) {

    this.out = new LinkedList<OutputStream>();

    for (Iterator<OutputStream> i = outStreams.iterator(); i.hasNext();) {
        OutputStream outputStream = (OutputStream) i.next();

        if(outputStream == null){
            throw new NullPointerException();
        }
        this.out.add(outputStream);
    }
}

@Override
public void write(int arg0) throws IOException {

    for (Iterator<OutputStream> i = out.iterator(); i.hasNext();) {
        OutputStream var = (OutputStream) i.next();

        var.write(arg0);
    }
}

@Override
public void write(byte[] b) throws IOException{

    for (Iterator<OutputStream> i = out.iterator(); i.hasNext();) {
        OutputStream var = (OutputStream) i.next();

        var.write(b);
    }
}

@Override
public void write(byte[] b, int off, int len) throws IOException{

    for (Iterator<OutputStream> i = out.iterator(); i.hasNext();) {
        OutputStream var = (OutputStream) i.next();

        var.write(b, off, len);
    }
}

@Override
public void close() throws IOException{

    for (Iterator<OutputStream> i = out.iterator(); i.hasNext();) {
        OutputStream var = (OutputStream) i.next();

        var.close();
    }
}

@Override
public void flush() throws IOException{

    for (Iterator<OutputStream> i = out.iterator(); i.hasNext();) {
        OutputStream var = (OutputStream) i.next();

        var.flush();
    }
}

}

目前为止工作正常,只进行了一些基本操作测试,例如从System.out流设置MultiOutputStream以及2个PrintStreams,每个都写入一个单独的日志文件。我使用了

System.setOut(multiOutputStream);

我希望你能将以下内容翻译成中文:在我的终端屏幕和两个日志文件中写入内容,这些操作都应该没有任何问题。


我尝试使用你的代码,但是它给了我一个编译错误 - 类型System中的方法setOut(PrintStream)不适用于参数(MultiOutputStream)。 - Raj
@Raj 因为 OutputStream 不是 PrintStream,而是相反的。 - Dave Newton

2

自己动手实现很简单。使用一个ArrayList<OutputStream>或者其他流行的数据结构来存储所有需要写入的流,然后编写write方法循环遍历每个流,向它们写入数据即可。


18
在输入输出中,没有什么是微不足道的,即使一开始看起来似乎如此。 - Steve McLeod
1
@Kevin 不用了,for-each循环并不能解决同时写入的问题。for-each会按顺序写入流,而不是同时写入。 - rurouniwallace
@ZettaSuro 所以在线程中生成它们,但如果文件在同一物理磁盘上,则不会更快,而且可能会更慢,我相信这不是 OP 的意思。它确实在一个方法调用中写入所有流,这就是手头的问题。 - Kevin
我明白了...那么使用TeeOutputStream相比自己迭代每个输出流的优势是什么? - rurouniwallace
1
@ZettaSuro 一次方法调用代替每次循环。设置流以一次写入所有所需的流,然后只需使用teeos.write(stuff)即可。 - Kevin
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接