How to write data to two java.io.OutputStream objects at once?
Asked Answered
A

6

43

I'm looking for magical Java class that will allow me to do something like this:

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

MultiOutputStream outStream = new MultiOutputStream(byteStream, fileStream);

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

Basically, I want tee for OutputStreams in Java. Any ideas?

Thanks!

Alpheus answered 2/11, 2011 at 21:13 Comment(0)
D
47

Try the Apache Commons TeeOutputStream.

Democrat answered 2/11, 2011 at 21:16 Comment(0)
D
27

Just roll your own. There isn't any magic at all. Using Apache's TeeOutputStream you would basically use the code below. Of course using the Apache Commons I/O library you can leverage other classes, but sometimes it is nice to actually write something for yourself. :)

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();
    }
  }
}

Testing with the above class with the following

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();
}

would print Hello World!Hello World!.

(Note: the overridden close() could use some care tho' :)

Delight answered 2/11, 2011 at 21:35 Comment(7)
The writes should be synchronized.Democrat
You can simplify that. Have it extend FilterOutputStream; construct it with super(out); get rid of the out member; and change all the out. references to super. references.Rhymester
@Dave Depends on the use case. EJP I like it better this way, but that would work also.Bollard
@DaveNewton Mind explaining your comment about "writes should be synchronized", is it mandatory? Do regularl java io streams use a synchronized keyword?Bazooka
@Bazooka No, they don't. The issue here (as I saw it five years ago, anyway) is that since there are multiple writes, you'd probably want them to execute as a single unit to avoid potentially weird output result ordering. Could be wrong, though.Democrat
@KohányiRóbert There is something magical. Using code that lots of other people are using increases the likelihood that any bugs in the code are found and fixed. For example, when out.close() throws an IOException and tee.close() is skipped because exception handling did not ensure the second stream got closed.Buyers
@Buyers Hey, you're right about the close method. I've updated the code snippet just for reference. I still believe there's nothing magical about it though :)Bollard
P
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());
Plowshare answered 28/6, 2012 at 2:22 Comment(0)
B
4

Just found this thread beacause I had to face the same problem. If someone wants to see my solution (java7 code):

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();
    }
}

}

Works fine so far, just tested some basic operation, e.g. setting up a MultiOutputStream from the System.out Stream and 2 PrintStreams each writing into a seperate log. I used

System.setOut(multiOutputStream);

to write to my terminal screen and two logs which worked without any problems.

Buber answered 24/1, 2013 at 20:55 Comment(2)
i tried to used your code, however its giving me a compilation error - The method setOut(PrintStream) in the type System is not applicable for the arguments (MultiOutputStream)Shrove
@Shrove Because an OutputStream is not a PrintStream but the other way around.Democrat
S
2

Roll your own, it's basically trivial. Use an ArrayList<OutputStream> or whatever's popular nowadays to store all the streams you want and write the write method to loop over all of them, writing to each.

Secretin answered 2/11, 2011 at 21:15 Comment(9)
Nothing in I/O is ever trivial. Even if it seems so at first.Potentiate
This doesn't really answer the question of how to write to all of them simultaneous.Hairbrush
@Secretin No thanks. A for-each loop does not solve the problem of how to write them simultaneously. A for-each will write to the streams in sequence, they won't be written in parallel (i.e. simultaneously).Hairbrush
@ZettaSuro So spawn them in threads, but that would not be any faster if the files are on the same physical disk, and potentially slower, and I'm quite sure that's not what the OP meant. It does write all the streams in one method call, which is the problem at hand.Secretin
I'd bet quite a bit that if I look at the Apache TeeOutputStream source, it does them in series like this, not in parallel in separate threads.Secretin
Just checked, TOS is indeed in series, check the source hereSecretin
source: @override public synchronized void write(final int b) throws IOException { super.write(b); this.branch.write(b); }Secretin
I see...so then what is the advantage of using TeeOutputStream over simply iterating over each output stream yourself?Hairbrush
@ZettaSuro A single method call instead of a loop every time. Set up the stream once to write to all the streams you want, then just teeos.write(stuff).Secretin
C
0

Here's an example of TeeOutputStream usage (keep outputting stdout & stderr) and printing it to file:

public static void main(String[] args) throws IOException {

    System.setOut(new PrintStream(new TeeOutputStream(System.out, new PrintStream("out.log"))));
    System.setErr(new PrintStream(new TeeOutputStream(System.err, new PrintStream("err.log"))));
...
Commutative answered 25/6 at 15:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.