何らかの処理を行うためにメソッドに渡すInputStreamがあります。他のメソッドで同じInputStreamを使用しますが、最初の処理の後、InputStreamはメソッド内で閉じられているように見えます。
InputStreamを複製して、彼を閉じるメソッドに送信するにはどうすればよいですか?別の解決策はありますか?
編集: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";
}
}
同じ情報を複数回読み取り、入力データがメモリに収まるほど小さい場合は、InputStream
から ByteArrayOutputStream にデータをコピーできます。
次に、関連するバイト配列を取得し、必要なだけ「クローン」 ByteArrayInputStream sを開くことができます。
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()
メソッドを追跡し、何らかの方法で呼び出されないようにする必要があります。
Java 9以降、中間ビットは InputStream.transferTo
に置き換えることができます。
ByteArrayOutputStream baos = new ByteArrayOutputStream();
input.transferTo(baos);
InputStream firstClone = new ByteArrayInputStream(baos.toByteArray());
InputStream secondClone = new ByteArrayInputStream(baos.toByteArray());
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();
クローンを作成することはできません。問題の解決方法は、データのソースによって異なります。
1つの解決策は、InputStreamからすべてのデータをバイト配列に読み取り、そのバイト配列の周囲にByteArrayInputStreamを作成し、その入力ストリームをメソッドに渡すことです。
編集1:つまり、他のメソッドも同じデータを読み取る必要がある場合。つまり、ストリームを「リセット」したいのです。
ストリームから読み取られたデータが大きい場合、Apache Commons IOのTeeInputStreamを使用することをお勧めします。そうすれば、基本的に入力を複製し、t'dパイプをクローンとして渡すことができます。
これはすべての状況で機能するわけではありませんが、ここで私がやったことです: 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
のインスタンスを渡すだけで、入力ストリームで渡すことになります。元の入力ストリームをコンストラクターパラメーターとして使用します。
これはバイトごとに機能するため、高性能が必要な場合は使用しないでください
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)
入力ストリームの複製は、複製する入力ストリームの詳細に関する深い知識を必要とするため、良いアイデアではないかもしれません。これを回避するには、同じソースから再度読み取る新しい入力ストリームを作成します。
したがって、いくつかの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
にカプセル化された入力ストリームの作成。また、ストリームのクローンを作成するために2番目のコードパスを維持する必要はありません。
一方、ストリームからの読み取りが高価な場合(低帯域幅の接続で行われるため)、この方法はコストを2倍にします。これは、最初にストリームコンテンツをローカルに保存し、現在のローカルリソースにInputStream
を提供する特定のサプライヤを使用することで回避できます。
以下のクラスがトリックを行うはずです。インスタンスを作成し、「乗算」メソッドを呼び出して、ソース入力ストリームと必要な複製の量を提供します。
重要:クローンされたすべてのストリームを別々のスレッドで同時に使用する必要があります。
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();
}
}
}
}
}
以下は、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/