複数回使用するためにInputStreamをキャッシュする方法
-
06-09-2019 - |
質問
ファイルのInputStreamがあり、Apache poiコンポーネントを使用して次のようにそれから読み取ります。
POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);
問題は、同じストリームを複数回使用する必要があり、POIFSFileSystem が使用後にストリームを閉じることです。
入力ストリームからのデータをキャッシュし、さらに多くの入力ストリームを別の POIFSFileSystem に提供する最良の方法は何ですか?
編集1:
キャッシュとは、アプリケーションを高速化する方法ではなく、後で使用するために保存することを意味します。また、入力ストリームを配列または文字列に読み取って、使用するたびに入力ストリームを作成する方が良いでしょうか?
編集2:
再度質問して申し訳ありませんが、デスクトップと Web アプリケーション内で作業する場合は条件が多少異なります。まず第一に、Tomcat Web アプリの org.apache.commons.fileupload.FileItem から取得した InputStream はマーキングをサポートしていないため、リセットできません。
次に、ファイルを処理する際のアクセスを高速化し、IO の問題を軽減するために、ファイルをメモリ内に保持できるようにしたいと考えています。
解決
あなたは近いが()それが(リセットで応答と呼ばれていることのバージョンでのPOIFSFileSystem のに渡されるInputStreamを)飾ることができます:
class ResetOnCloseInputStream extends InputStream {
private final InputStream decorated;
public ResetOnCloseInputStream(InputStream anInputStream) {
if (!anInputStream.markSupported()) {
throw new IllegalArgumentException("marking not supported");
}
anInputStream.mark( 1 << 24); // magic constant: BEWARE
decorated = anInputStream;
}
@Override
public void close() throws IOException {
decorated.reset();
}
@Override
public int read() throws IOException {
return decorated.read();
}
}
テストケース
static void closeAfterInputStreamIsConsumed(InputStream is)
throws IOException {
int r;
while ((r = is.read()) != -1) {
System.out.println(r);
}
is.close();
System.out.println("=========");
}
public static void main(String[] args) throws IOException {
InputStream is = new ByteArrayInputStream("sample".getBytes());
ResetOnCloseInputStream decoratedIs = new ResetOnCloseInputStream(is);
closeAfterInputStreamIsConsumed(decoratedIs);
closeAfterInputStreamIsConsumed(decoratedIs);
closeAfterInputStreamIsConsumed(is);
}
EDIT 2
あなたはバイト[](吸い込みモード)でファイル全体を読み込むことができ、その後に、ByteArrayInputStreamに渡し
他のヒント
マークを追加するにBufferedInputStreamを、試してみて、別の入力ストリームに機能をリセットし、ちょうどそのcloseメソッドをオーバーライドします:
public class UnclosableBufferedInputStream extends BufferedInputStream {
public UnclosableBufferedInputStream(InputStream in) {
super(in);
super.mark(Integer.MAX_VALUE);
}
@Override
public void close() throws IOException {
super.reset();
}
}
ですからます:
UnclosableBufferedInputStream bis = new UnclosableBufferedInputStream (inputStream);
、InputStreamでは、以前に使用された場所bis
を使用します。
これは正しく動作します:
byte[] bytes = getBytes(inputStream);
POIFSFileSystem fileSystem = new POIFSFileSystem(new ByteArrayInputStream(bytes));
ここでGetBytesメソッドは次のようになります。
private static byte[] getBytes(InputStream is) throws IOException {
byte[] buffer = new byte[8192];
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
int n;
baos.reset();
while ((n = is.read(buffer, 0, buffer.length)) != -1) {
baos.write(buffer, 0, n);
}
return baos.toByteArray();
}
より多くのカスタム使用の実装の下に使用します -
public class ReusableBufferedInputStream extends BufferedInputStream
{
private int totalUse;
private int used;
public ReusableBufferedInputStream(InputStream in, Integer totalUse)
{
super(in);
if (totalUse > 1)
{
super.mark(Integer.MAX_VALUE);
this.totalUse = totalUse;
this.used = 1;
}
else
{
this.totalUse = 1;
this.used = 1;
}
}
@Override
public void close() throws IOException
{
if (used < totalUse)
{
super.reset();
++used;
}
else
{
super.close();
}
}
}
あなたは「キャッシュ」と正確に何を意味するのですか?あなたは別のPOIFSFileSystemは、ストリームの先頭から開始したいですか?もしそうなら、あなたのJavaコードには何もキャッシングない点は絶対にありません。それだけで新しいストリームを開いて、OSによって行われます。
それとも、最初POIFSFileSystemが停止時点で読み続けワンのですか?それは、キャッシングではありませんし、それを行うのは非常に困難です。あなたが閉じたばかりのストリームを避けることができない場合、私は考えることができる唯一の方法は、読んで、新しいストリームをオープンし、その多くのバイトをスキップしてきたどのように多くのバイト数をカウント薄いラッパーを書くことであろう。 POIFSFileSystemが内部にBufferedInputStreamのようなものを使用している場合しかし、それは失敗する可能性があります。
ファイルがその大きなされていない場合、byte[]
配列にそれを読んで、POIにその配列から作成されたByteArrayInputStream
を与えます。
、あなたは、気にしてはいけません。
[EDIT] ANでバイト配列にファイルを読み取るためにのApache Commonsの-IO に使用します効率的な方法。それはバイトで、ファイルのバイトを読み込んでいるためであるint read()
を使用しないでください。の非常にの遅い!
あなたはそれを自分で行いたい場合は、アレイと、ファイルからバイトを読み込み、ループを作成し、長さを取得するためにFile
オブジェクトを使用します。あなたはread(byte[], int offset, int len)
以来、ループはlen
バイト未満を読んで(通常)できる必要があります。
これは、任意の InputStream で安全に使用できるように実装する方法です。
- オリジナルのストリームコンテンツをミラーリングする一時ファイルを作成する独自のInputStreamラッパーを作成します。
- 元の入力ストリームから読み取られたすべてのものをこの一時ファイルにダンプします
- ストリームが完全に読み取られると、すべてのデータが一時ファイルにミラーリングされます。
- InputStream.reset を使用して内部ストリームを FileInputStream(mirrored_content_file) に切り替え(初期化)ます。
- これ以降、元のストリームの参照が失われます(収集可能)
- 一時ファイルを削除し、開いているストリームを解放する新しいメソッド release() を追加します。
- release() を呼び出すこともできます 完成させる release() を呼び出すのを忘れた場合に備えて、一時ファイルが確実にリリースされるようにするため (ほとんどの場合、この関数の使用は避けるべきです) 完成させる, 、常にオブジェクト リソースを解放するメソッドを呼び出します)。見る なぜ、finalize() を実装するのでしょうか?
public static void main(String[] args) throws IOException {
BufferedInputStream inputStream = new BufferedInputStream(IOUtils.toInputStream("Foobar"));
inputStream.mark(Integer.MAX_VALUE);
System.out.println(IOUtils.toString(inputStream));
inputStream.reset();
System.out.println(IOUtils.toString(inputStream));
}
この作品。 IOUtilsコモンズIOの一部です。
この答えは以前のものに反復する 1 に| <のhref = "https://stackoverflow.com / / 48136分の18959357" > 2 の BufferInputStream
に基づきます。主な変更点は、それが無限の再利用を可能にするということです。そして、自由アップするためにシステムリソースを元のソースの入力ストリームを閉じるの面倒を見ます。 (それはまたですあなたは常にすべき理由EntityUtils.consumeQuietly()
Apacheで、例えば応答を「消費」<全角> の)あなたのOSは、これらの制限を定義すると、あなたは、プログラムがファイルハンドルが不足したくありません。 編集その場合には、それはそれread(buffer, offset, length)
はソースを見て、ハードしようと発生する可能性があり、このコードは、その使用から保護、BufferedInputStream
を使用gready消費者のために処理するコードを更新します。
public class CachingInputStream extends BufferedInputStream {
public CachingInputStream(InputStream source) {
super(new PostCloseProtection(source));
super.mark(Integer.MAX_VALUE);
}
@Override
public synchronized void close() throws IOException {
if (!((PostCloseProtection) in).decoratedClosed) {
in.close();
}
super.reset();
}
private static class PostCloseProtection extends InputStream {
private volatile boolean decoratedClosed = false;
private final InputStream source;
public PostCloseProtection(InputStream source) {
this.source = source;
}
@Override
public int read() throws IOException {
return decoratedClosed ? -1 : source.read();
}
@Override
public int read(byte[] b) throws IOException {
return decoratedClosed ? -1 : source.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return decoratedClosed ? -1 : source.read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
return decoratedClosed ? 0 : source.skip(n);
}
@Override
public int available() throws IOException {
return source.available();
}
@Override
public void close() throws IOException {
decoratedClosed = true;
source.close();
}
@Override
public void mark(int readLimit) {
source.mark(readLimit);
}
@Override
public void reset() throws IOException {
source.reset();
}
@Override
public boolean markSupported() {
return source.markSupported();
}
}
}
それがなかった場合だけで最初にそれを閉じて、それを再利用します。
一つの制限は、しかし、元のストリームの全体の内容が読み込まれた前に、ストリームが閉じている場合は、このデコレータは、不完全なデータを持っているので、全体の流れを閉じる前に読まれることを確認するということです。
私は、ここに私の解決策を追加します。これは、基本的には上の2つの答えの組み合わせです:)
private String convertStreamToString(InputStream is) {
Writer w = new StringWriter();
char[] buf = new char[1024];
Reader r;
is.mark(1 << 24);
try {
r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
int n;
while ((n=r.read(buf)) != -1) {
w.write(buf, 0, n);
}
is.reset();
} catch(UnsupportedEncodingException e) {
Logger.debug(this.getClass(), "Cannot convert stream to string.", e);
} catch(IOException e) {
Logger.debug(this.getClass(), "Cannot convert stream to string.", e);
}
return w.toString();
}