Как клонировать InputStream?
У меня есть InputStream, который я передаю методу для обработки. Я буду использовать тот же InputStream в другом методе, но после первой обработки InputStream будет закрыт внутри метода.
Как я могу клонировать InputStream для отправки методу, который его закрывает? Есть еще одно решение?
EDIT: методы, которые закрывают InputStream, являются внешним методом из lib. У меня нет контроля над закрытием или нет.
private String getContent(HttpURLConnection con) {
InputStream content = null;
String charset = "";
try {
content = con.getInputStream();
CloseShieldInputStream csContent = new CloseShieldInputStream(content);
charset = getCharset(csContent);
return IOUtils.toString(content,charset);
} catch (Exception e) {
System.out.println("Error downloading page: " + e);
return null;
}
}
private String getCharset(InputStream content) {
try {
Source parser = new Source(content);
return parser.getEncoding();
} catch (Exception e) {
System.out.println("Error determining charset: " + e);
return "UTF-8";
}
}
Ответы
Ответ 1
Если все, что вы хотите сделать, это прочитать одну и ту же информацию более одного раза, а входные данные достаточно малы, чтобы поместиться в память, вы можете скопировать данные из вашего InputStream
в ByteArrayOutputStream.
Затем вы можете получить связанный массив байтов и открыть столько "клонированных" ByteArrayInputStream, сколько захотите.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Fake code simulating the copy
// You can generally do better with nio if you need...
// And please, unlike me, do something about the Exceptions :D
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) > -1 ) {
baos.write(buffer, 0, len);
}
baos.flush();
// Open new InputStreams using the recorded bytes
// Can be repeated as many times as you wish
InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
InputStream is2 = new ByteArrayInputStream(baos.toByteArray());
Но если вам действительно нужно оставить исходный поток открытым для получения новых данных, то вам нужно будет отследить этот внешний метод close()
и предотвратить его каким-либо образом вызывать.
ОБНОВЛЕНИЕ (2019):
Начиная с Java 9, средние биты можно заменить на InputStream.transferTo
:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
input.transferTo(baos);
InputStream firstClone = new ByteArrayInputStream(baos.toByteArray());
InputStream secondClone = new ByteArrayInputStream(baos.toByteArray());
Ответ 2
Вы хотите использовать Apache CloseShieldInputStream
:
Это оболочка, которая предотвратит закрытие потока. Вы бы сделали что-то подобное.
InputStream is = null;
is = getStream(); //obtain the stream
CloseShieldInputStream csis = new CloseShieldInputStream(is);
// call the bad function that does things it shouldn't
badFunction(csis);
// happiness follows: do something with the original input stream
is.read();
Ответ 3
Вы не можете клонировать его, и как вы решите свою проблему, зависит от источника данных.
Одним из решений является чтение всех данных из InputStream в массив байтов, а затем создание ByteArrayInputStream вокруг этого байтового массива и передача этого входного потока в ваш метод.
Изменить 1:
То есть, если другой метод также должен читать одни и те же данные. Вы хотите "reset" поток.
Ответ 4
Если данные, прочитанные из потока, большие, я бы рекомендовал использовать TeeInputStream из Apache Commons IO. Таким образом, вы можете по существу реплицировать вход и передать t'd-канал в качестве своего клона.
Ответ 5
Это может не работать во всех ситуациях, но вот что я сделал: я расширил класс FilterInputStream и выполнил требуемую обработку байты, поскольку внешняя библиотека считывает данные.
public class StreamBytesWithExtraProcessingInputStream extends FilterInputStream {
protected StreamBytesWithExtraProcessingInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int readByte = super.read();
processByte(readByte);
return readByte;
}
@Override
public int read(byte[] buffer, int offset, int count) throws IOException {
int readBytes = super.read(buffer, offset, count);
processBytes(buffer, offset, readBytes);
return readBytes;
}
private void processBytes(byte[] buffer, int offset, int readBytes) {
for (int i = 0; i < readBytes; i++) {
processByte(buffer[i + offset]);
}
}
private void processByte(int readByte) {
// TODO do processing here
}
}
Затем вы просто передаете экземпляр StreamBytesWithExtraProcessingInputStream
, в который вы должны были пройти во входном потоке. С исходным входным потоком в качестве параметра конструктора.
Следует отметить, что этот байт работает для байта, поэтому не используйте его, если требуется высокая производительность.
Ответ 6
Если вы используете apache.commons
, вы можете копировать потоки с помощью IOUtils
.
Вы можете использовать следующий код:
InputStream = IOUtils.toBufferedInputStream(toCopy);
Вот полный пример, подходящий для вашей ситуации:
public void cloneStream() throws IOException{
InputStream toCopy=IOUtils.toInputStream("aaa");
InputStream dest= null;
dest=IOUtils.toBufferedInputStream(toCopy);
toCopy.close();
String result = new String(IOUtils.toByteArray(dest));
System.out.println(result);
}
Этот код требует некоторых зависимостей:
MAVEN
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
GRADLE
'commons-io:commons-io:2.4'
Вот ссылка DOC для этого метода:
Выбирает все содержимое InputStream и отображает те же данные, что и результат InputStream. Этот метод полезен там, где
Источник InputStream работает медленно. Он связан с сетевыми ресурсами, поэтому мы не может держать его открытым в течение длительного времени. Он связан с сетевым таймаутом.
Вы можете найти более подробную информацию о IOUtils
здесь:
http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStream(java.io.InputStream)
Ответ 7
Клонирование входного потока может не быть хорошей идеей, потому что это требует глубоких знаний о деталях клонирования входного потока. Обходным путем для этого является создание нового потока ввода, который снова считывается из того же источника.
Итак, используя некоторые функции Java 8, это будет выглядеть так:
public class Foo {
private Supplier<InputStream> inputStreamSupplier;
public void bar() {
procesDataThisWay(inputStreamSupplier.get());
procesDataTheOtherWay(inputStreamSupplier.get());
}
private void procesDataThisWay(InputStream) {
// ...
}
private void procesDataTheOtherWay(InputStream) {
// ...
}
}
Этот метод положительно влияет на то, что он будет повторно использовать уже существующий код - создание входного потока, заключенного в inputStreamSupplier
. И нет необходимости поддерживать второй путь кода для клонирования потока.
С другой стороны, если чтение из потока является дорогостоящим (поскольку оно выполняется через соединение с низкой пропускной способностью), этот метод удваивает затраты. Это можно обойти, используя конкретный поставщик, который сначала сохранит содержимое потока локально и предоставит InputStream
для этого локального ресурса.
Ответ 8
Ниже приведено решение с Котлином.
Вы можете скопировать ваш InputStream в ByteArray
val inputStream = ...
val byteOutputStream = ByteArrayOutputStream()
inputStream.use { input ->
byteOutputStream.use { output ->
input.copyTo(output)
}
}
val byteInputStream = ByteArrayInputStream(byteOutputStream.toByteArray())
Если вам нужно прочитать byteInputStream
несколько раз, вызовите byteInputStream.reset()
перед чтением снова.
https://code.luasoftware.com/tutorials/kotlin/how-to-clone-inputstream/
Ответ 9
Следующий класс должен сделать трюк. Просто создайте экземпляр, вызовите метод "multiply" и укажите исходный поток ввода и количество дубликатов, которые вам нужны.
Важно: вы должны использовать все клонированные потоки одновременно в отдельных потоках.
package foo.bar;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class InputStreamMultiplier {
protected static final int BUFFER_SIZE = 1024;
private ExecutorService executorService = Executors.newCachedThreadPool();
public InputStream[] multiply(final InputStream source, int count) throws IOException {
PipedInputStream[] ins = new PipedInputStream[count];
final PipedOutputStream[] outs = new PipedOutputStream[count];
for (int i = 0; i < count; i++)
{
ins[i] = new PipedInputStream();
outs[i] = new PipedOutputStream(ins[i]);
}
executorService.execute(new Runnable() {
public void run() {
try {
copy(source, outs);
} catch (IOException e) {
e.printStackTrace();
}
}
});
return ins;
}
protected void copy(final InputStream source, final PipedOutputStream[] outs) throws IOException {
byte[] buffer = new byte[BUFFER_SIZE];
int n = 0;
try {
while (-1 != (n = source.read(buffer))) {
//write each chunk to all output streams
for (PipedOutputStream out : outs) {
out.write(buffer, 0, n);
}
}
} finally {
//close all output streams
for (PipedOutputStream out : outs) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}