web-dev-qa-db-ja.com

複数使用のためにInputStreamをキャッシュする方法

ファイルのInputStreamがあり、Apache poiコンポーネントを使用して、次のようにファイルから読み取ります。

POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);

問題は、同じストリームを複数回使用する必要があり、POIFSFileSystemが使用後にストリームを閉じることです。

入力ストリームからのデータをキャッシュしてから、別のPOIFSFileSystemにより多くの入力ストリームを提供する最良の方法は何ですか?

編集1:

キャッシュとは、アプリケーションを高速化する方法としてではなく、後で使用するためのストアを意味します。また、入力ストリームを配列または文字列に読み取ってから、使用ごとに入力ストリームを作成する方が良いでしょうか?

編集2:

質問を再度開いて申し訳ありませんが、デスクトップおよびWebアプリケーション内で作業する場合の条件は多少異なります。まず、Tomcat Webアプリのorg.Apache.commons.fileupload.FileItemから取得したInputStreamはマーキングをサポートしていないため、リセットできません。

第二に、ファイルを処理するときのアクセスを高速化し、IOの問題を少なくするために、ファイルをメモリに保持できるようにしたいと考えています。

25
Azder

(POIFSFileSystemに渡されるInputStreamを、close()が呼び出されたときにreset()で応答するバージョンで装飾できます。

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

編集2

ファイル全体をbyte [](スラップモード)で読み取り、それをByteArrayInputStreamに渡すことができます。

18
dfa

マークおよびリセット機能を別の入力ストリームに追加する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を使用します。

20
Tomasz

これは正しく動作します:

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();
 }
5
Kaba Aboubacar

よりカスタム使用するには、以下の実装を使用してください-

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();
        }
    }
}
2
user2807207

ファイルがそれほど大きくない場合は、それを_byte[]_配列に読み込み、その配列から作成されたByteArrayInputStreamをPOIに与えます。

ファイルが大きい場合は、OSができる限り最善のキャッシュを行うため、気にする必要はありません。

[編集] Apache commons-io を使用して、効率的な方法でファイルをバイト配列に読み込みます。 int read()は、ファイルをバイト単位で読み取るため、使用しないでください非常に遅い

自分でやりたい場合は、Fileオブジェクトを使用して長さを取得し、配列と、ファイルからバイトを読み取るループを作成します。 read(byte[], int offset, int len)lenバイト未満を読み取ることができるため、ループする必要があります(通常はそうします)。

1
Aaron Digulla

「キャッシュ」とはどういう意味ですか?別のPOIFSFileSystemをストリームの最初から開始しますか?もしそうなら、あなたのJava=コードに何かをキャッシュすることは全く意味がありません。それはOSによって行われ、単に新しいストリームを開くだけです。

または、最初のPOIFSFileSystemが停止した時点で読み続けたいですか?これはキャッシュではなく、実行するのが非常に困難です。ストリームが閉じられないようにすることができない場合に考えられる唯一の方法は、読み取られたバイト数をカウントする薄いラッパーを書き込んでから、新しいストリームを開いてそのバイト数をスキップすることです。しかし、POIFSFileSystemが内部でBufferedInputStreamのようなものを使用している場合、失敗する可能性があります。

1
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はCommons IOの一部です。

1
Daniel Kaplan

これは、任意のInputStreamで安全に使用できるように実装する方法です。

  • 独自のInputStreamラッパーを記述して、元のストリームコンテンツをミラーリングする一時ファイルを作成します
  • 元の入力ストリームから読み取ったすべてをこの一時ファイルにダンプします
  • ストリームが完全に読み取られると、すべてのデータが一時ファイルにミラーリングされます
  • inputStream.resetを使用して、内部ストリームをFileInputStream(mirrored_content_file)に切り替えます(初期化)
  • 今後、元のストリームの参照が失われます(収集できます)
  • 一時ファイルを削除し、開いているストリームを解放する新しいメソッドrelease()を追加します。
  • finalizeからrelease()を呼び出して、release()(ほとんどの場合、 finalizeの使用は避けてください。常にメソッドを呼び出してオブジェクトリソースを解放します)。参照 finalize()を実装する理由
1
adrian.tarau

この答えは以前のものを繰り返します 1 | 2BufferInputStreamに基づいています。主な変更点は、無限に再利用できることです。また、元のソース入力ストリームを閉じてシステムリソースを解放します。あなたのOSはそれらの制限を定義し、プログラムがファイルハンドルを使い果たしたくない()それは、例えばApache EntityUtils.consumeQuietly()で常に応答を「消費する」べきである理由でもあります)。 [〜#〜] edit [〜#〜]その場合、read(buffer, offset, length)を使用するgreadyコンシューマーを処理するようにコードを更新しましたBufferedInputStreamがソースを一生懸命調べようとする場合があります。このコードはその使用から保護します。

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

再利用しない場合は、最初に閉じてください。

ただし、1つの制限は、元のストリームのコンテンツ全体が読み取られる前にストリームが閉じられると、このデコレータには不完全なデータが含まれるため、閉じる前にストリーム全体が読み取られることを確認してください。

1
Brice

これでうまくいくので、ここにソリューションを追加します。基本的には、上位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();
}
0
FuePi