ファイルのInputStreamがあり、Apache poiコンポーネントを使用して、次のようにファイルから読み取ります。
POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);
問題は、同じストリームを複数回使用する必要があり、POIFSFileSystemが使用後にストリームを閉じることです。
入力ストリームからのデータをキャッシュしてから、別のPOIFSFileSystemにより多くの入力ストリームを提供する最良の方法は何ですか?
編集1:
キャッシュとは、アプリケーションを高速化する方法としてではなく、後で使用するためのストアを意味します。また、入力ストリームを配列または文字列に読み取ってから、使用ごとに入力ストリームを作成する方が良いでしょうか?
編集2:
質問を再度開いて申し訳ありませんが、デスクトップおよびWebアプリケーション内で作業する場合の条件は多少異なります。まず、Tomcat Webアプリのorg.Apache.commons.fileupload.FileItemから取得したInputStreamはマーキングをサポートしていないため、リセットできません。
第二に、ファイルを処理するときのアクセスを高速化し、IOの問題を少なくするために、ファイルをメモリに保持できるようにしたいと考えています。
(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);
}
ファイル全体をbyte [](スラップモード)で読み取り、それを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();
}
}
}
ファイルがそれほど大きくない場合は、それを_byte[]
_配列に読み込み、その配列から作成されたByteArrayInputStream
をPOIに与えます。
ファイルが大きい場合は、OSができる限り最善のキャッシュを行うため、気にする必要はありません。
[編集] Apache commons-io を使用して、効率的な方法でファイルをバイト配列に読み込みます。 int read()
は、ファイルをバイト単位で読み取るため、使用しないでください非常に遅い!
自分でやりたい場合は、File
オブジェクトを使用して長さを取得し、配列と、ファイルからバイトを読み取るループを作成します。 read(byte[], int offset, int len)
はlen
バイト未満を読み取ることができるため、ループする必要があります(通常はそうします)。
「キャッシュ」とはどういう意味ですか?別のPOIFSFileSystemをストリームの最初から開始しますか?もしそうなら、あなたのJava=コードに何かをキャッシュすることは全く意味がありません。それはOSによって行われ、単に新しいストリームを開くだけです。
または、最初のPOIFSFileSystemが停止した時点で読み続けたいですか?これはキャッシュではなく、実行するのが非常に困難です。ストリームが閉じられないようにすることができない場合に考えられる唯一の方法は、読み取られたバイト数をカウントする薄いラッパーを書き込んでから、新しいストリームを開いてそのバイト数をスキップすることです。しかし、POIFSFileSystemが内部でBufferedInputStreamのようなものを使用している場合、失敗する可能性があります。
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の一部です。
これは、任意のInputStreamで安全に使用できるように実装する方法です。
この答えは以前のものを繰り返します 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つの制限は、元のストリームのコンテンツ全体が読み取られる前にストリームが閉じられると、このデコレータには不完全なデータが含まれるため、閉じる前にストリーム全体が読み取られることを確認してください。
これでうまくいくので、ここにソリューションを追加します。基本的には、上位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();
}