As it was said that the methods doSomething
and doSomethingElse
consume all the stream, so I understand that you both need all of her content.
Therefore, an alternative is - as stated in another answer - read all the contents of InputStream
and save it in an array of bytes. In the other answer was given the solution with Java 9 (with transferTo
), but for earlier versions you would have to read the content manually, and write it in a ByteArrayOutputStream
:
void readStream(InputStream input) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) > 0) {
baos.write(buffer, 0, len);
}
baos.flush();
var result1 = doSomething(new ByteArrayInputStream(baos.toByteArray()));
var result2 = doSomethingElse(new ByteArrayInputStream(baos.toByteArray()));
// etc...
}
With this, I get an array of bytes (in the above case, it is the array returned by baos.toByteArray()
), which I can then pass on to the methods, encapsulated in a ByteArrayInputStream
. So every call is made with a stream new but always containing the same data.
What you could do to improve - if you already "know" or have a sense of the approximate size of the data - is to create the ByteArrayOutputStream
with the proper size (something like new ByteArrayOutputStream(tamanhoDosDados)
), so you avoid the relocations that are made during the writing: documentation says the value default of the initial capacity is 32 (ie only 32 bytes), so by passing the size closer to the real you minimize the amount of times the internal array is reallocated.
Remember that this only applies if you have enough memory to store all the data from stream.
But if there is not enough memory, you need to read it more than once and prevent it from being closed (and do not want to reopen it), you could do a "gambiarra": create a stream that when it is closed, it actually resets. Something like this:
// solução ruim, veja considerações mais abaixo
public class ResetOnCloseInputStream extends InputStream {
private InputStream in;
public ResetOnCloseInputStream(InputStream in) {
this.in = in;
}
@Override
public int read() throws IOException {
return in.read();
}
// gambiarra: ao ser fechado, na verdade é resetado
@Override
public void close() throws IOException {
if (in.markSupported()) {
in.mark(0);
in.reset();
}
}
}
...
ResetOnCloseInputStream resetIn = new ResetOnCloseInputStream(input);
var result1 = doSomething(resetIn);
var result2 = doSomethingElse(resetIn);
But there are some problems: in addition to being a gambiarra, for being (in my opinion) misrepresenting the objective of the method close
, injuring both the semantics of this and the The Least Surprising Principle, not all stream can withstand the operations of mark
and reset
. So it’s a less than certain method that works, and I just leave it on record here as curiosity.
If there is not enough memory to maintain the byte array, another option is to write the contents of the input
in a temporary file, and at each call of the methods that consume the data, create a new stream that reads from this archive.
From Java 7, you can use bundle java.nio
:
InputStream input = // input original
// salva os dados do input em um arquivo temporário
Path tempFile = Files.createTempFile("prefixo", "sufixo"); // prefixo e sufixo podem ser null (tanto faz, o arquivo é temporário e o Java cria um nome "único" mesmo...)
Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);
// usa o arquivo temporário (cria um novo stream para cada método)
doSomething(Files.newInputStream(tempFile, StandardOpenOption.READ));
doSomethingElse(Files.newInputStream(tempFile, StandardOpenOption.READ));
// opcional: apaga o arquivo depois que terminar o processamento
Files.delete(tempFile);
For Java < 7, use java.io
even:
// copia os dados do input para um arquivo temporário
File tempFile = File.createTempFile("prefixo", "sufixo");
try (OutputStream out = new FileOutputStream(tempFile)) {
byte[] buffer = new byte[1024];
int read;
while ((read = input.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} catch (IOException e) {
// trata erros, etc
}
// usa o arquivo temporário (cria um novo stream para cada método)
doSomething(new FileInputStream(tempFile));
doSomethingElse(new FileInputStream(tempFile));
// opcional: apaga o arquivo depois que terminar o processamento
tempFile.delete();
Finally, if there’s no way to keep the data in memory or create the temporary file, the way is to do what you don’t seem to want: reopen the stream and read it again.
There is also the option to use an external library, as already suggested in the other answer, but it has some poréns:
About Apache Commons
As it has been implied that the methods that process the stream read all the data of this, then CloseShieldInputStream
- at least in the tests I’ve done - it doesn’t seem like a good solution:
InputStream input = // o input original
CloseShieldInputStream closeShieldForInput = new CloseShieldInputStream(input);
var result1 = doSomething(closeShieldForInput); // leu todos os dados do input
var result2 = doSomethingElse(closeShieldForInput); // não leu nada
In the tests I did, I created the methods doSomething
and doSomethingElse
so that they read all the data from stream and close it at the end (basically, "while (read) etc
and close
"). With that, only doSomething
read the data from input
, but doSomethingElse
couldn’t read any more.
This happens because what CloseShieldInputStream
is only to prevent the InputStream
that it encapsulates be closed. If we look at the source code, we will see that in fact the method close
simply arrow the InputStream
for a ClosedInputStream
(and this class, in turn, has a method read
who always returns -1
- that is, in practice it is like a stream "empty", with no data).
That’s why after closing one CloseShieldInputStream
, the input
original is not closed, but the attempt to read something afterwards brings no data. So I understand that this does not suit your case. A documentation says the following:
This class is typically used in cases Where an input stream needs to be passed to a Component that wants to explicitly close the stream Even if more input would still be available to other Components.
That is, it is useful when the stream is closed before having all data read, but you do not want it to be closed, and you want to continue reading from where you left off.
For example, I created a file containing the text abc
and took that test:
InputStream input = new FileInputStream("arquivo.txt"); // arquivo contendo "abc"
CloseShieldInputStream closeShieldForInput = new CloseShieldInputStream(input);
System.out.println(closeShieldForInput.read()); // 97 <- leu a letra "a"
System.out.println(closeShieldForInput.read()); // 98 <- leu a letra "b"
// não fecha o input original, mas seta o stream interno para um ClosedInputStream
closeShieldForInput.close();
System.out.println(closeShieldForInput.read()); // -1 <- a leitura está sendo feita no ClosedInputStream
System.out.println(closeShieldForInput.read()); // -1 <- a leitura está sendo feita no ClosedInputStream
// ao ler do input original, continua de onde parou (já que ele não foi fechado)
System.out.println(input.read()); // 99 <- leu a letra "c"
Note that after the CloseShieldInputStream
be closed, calls to read
return -1. But if I read directly from input
original, it remains open and the data is read correctly, from where it had left off.
So this is not a good solution in case you need to consume the stream whole several times. You could even try resetting the stream before reusing it, but falls into the problem already mentioned above (not all streams support reset
), and besides, after the first reset
you would have to use the input
original instead of the CloseShieldInputStream
(or else create another).
About the TeeInputStream
, it actually works, in conjunction with PipedInputStream
and PipedOutputStream
:
InputStream input = // o input original
PipedInputStream in = new PipedInputStream();
TeeInputStream tee = new TeeInputStream(input, new PipedOutputStream(in));
var result1 = doSomething(tee);
var result2 = doSomethingElse(in);
But remember that internally, a copy of the data is being made: when reading from TeeInputStream
, send the data to the PipedOutputStream
, which in turn sends the data to PipedInputStream
, that keeps a copy of them (that is, if there is memory limitation, this solution would not apply either).
And there is another limitation: if you want to read the data again (for the third time), it will not be possible:
InputStream input = // o input original
PipedInputStream in = new PipedInputStream();
TeeInputStream tee = new TeeInputStream(input, new PipedOutputStream(in));
var result1 = doSomething(tee);
var result2 = doSomethingElse(in);
var result3 = doAnotherThing(tee); // erro: tee está fechado
var result4 = doOneMoreThing(in); // erro: in está fechado
var result5 = doJustOneMoreThing(input); // erro: input está fechado
For both the TeeInputStream
as to the PipedInputStream
(and the input
original) will be closed.
Remember that this does not occur with solutions with ByteArrayInputStream
and with the temporary file: with them, we can create as many as necessary. So those remain - in my opinion - the best options: if there is enough memory to maintain the byte array, ByteArrayInputStream
it is preferable, otherwise prefer to create the temporary file.
+1 Very cool! Just a question. I even thought about asking in META, but for what you have done, you are welcome to ask a question/answer couple? Because I have a case that I did not find the answer on a specific subject here at Sopt and I had to look at documentation and at Soen to be able to do an analysis for my solution. In that case, it would be good for me to post a question/answer pair?
– Cmte Cardeal
I don’t know (yet) if there is anything in the META on the subject...
– Cmte Cardeal
@Cmtecardeal Yes, answer the question itself is perfectly normal and within the rules - myself have already done that
– hkotsubo