Как клонировать входной поток?
У меня есть 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";
}
}
9 ответов:
Если все, что вы хотите сделать, это прочитать одну и ту же информацию более одного раза, и входные данные достаточно малы, чтобы вписаться в память, вы можете скопировать данные из вашего
InputStream
до ByteArrayOutputStream.затем вы можете получить связанный массив байтов и открыть столько же "клонированных"ByteArrayInputStreams, как вам нравится.
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()
метод и предотвратить его от вызова как-то.
вы хотите использовать 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();
вы не можете клонировать его, и как вы собираетесь решить вашу проблему зависит от того, какой источник данных.
одним из решений является чтение всех данных из входного потока в массив байтов, а затем создание ByteArrayInputStream вокруг этого массива байтов и передача этого входного потока в ваш метод.
изменить 1: То есть, если другой метод также должен прочитать те же данные. То есть вы хотите "сбросить" поток.
Если данные, считываемые из потока, большие, я бы рекомендовал использовать TeeInputStream из Apache Commons IO. Таким образом, вы можете по существу реплицировать вход и передать канал t'D в качестве своего клона.
Это может не работать во всех ситуациях, но вот что я сделал: я продлила FilterInputStream класс и выполнить необходимую обработку байтов, как внешний lib считывает данные.
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
где вы бы прошли во входном потоке. С исходным входным потоком в качестве параметра конструктора.следует отметить, что это работает байт за байтом, так что не используйте это, если высокий производительность является обязательным требованием.
если вы используете
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>
ш
'commons-io:commons-io:2.4'
вот ссылка DOC для этого метода:
извлекает все содержимое входного потока и представляют те же данные, что InputStream в результате. Этот метод полезен, когда
источник InputStream медленно. Он имеет связанные сетевые ресурсы, поэтому мы нельзя держать его открытым в течение длительного времени. Он имеет сетевой тайм-аут, связанный.
вы можете найти больше о
IOUtils
здесь: http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStream(java.io.InputStream)
клонирование входного потока может быть не очень хорошей идеей, потому что это требует глубоких знаний о деталях клонируемого входного потока. Обходным путем для этого является создание нового входного потока, который считывает из того же источника снова.
Так что с помощью некоторых функций 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
для этого местных ресурсов.
ниже Решение с Котлин.
вы можете скопировать свой 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/
класс ниже должен сделать трюк. Просто создайте экземпляр, вызовите метод "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(); } } } } }