Как клонировать 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";
    }
}

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

вы не можете клонировать его, и то, как вы собираетесь решить свою проблему, зависит от источника данных.

одно из решений-прочитать все данные из InputStream в массив байтов, а затем создать ByteArrayInputStream вокруг этого массива байтов и передать этот входной поток в ваш метод.

изменить 1: То есть, если другой метод также должен читать те же данные. Т. е. вы хотите "сбросить" поток.


Если данные, считываемые из потока, большие, я бы рекомендовал использовать TeeInputStream из Apache Commons IO. Таким образом, вы можете по существу реплицировать вход и передать канал t'D в качестве клона.


Это может не работать во всех ситуациях, но вот что я сделал: я продлила FilterInputStream class и выполните требуемую обработку байтов, поскольку внешний 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 в результате. Этот метод полезен, когда

Source 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 для этого местных ресурсов.


Ниже приведено решение с Kotlin.

вы можете скопировать 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();
                }
            }
        }
    }
}