Como Cache InputStream para Uso Múltiplo
-
06-09-2019 - |
Pergunta
Eu tenho um InputStream de um arquivo e eu usar o Apache componentes poi para lê-lo como este:
POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);
O problema é que eu preciso para usar as mesmas múltiplas fluxo de vezes eo POIFSFileSystem fecha o fluxo após o uso.
O que é a melhor maneira de armazenar em cache os dados do fluxo de entrada e, em seguida, servir fluxos mais entrada para POIFSFileSystem diferente?
EDIT 1:
Por esconderijo eu quis dizer loja para uso posterior, e não como uma maneira para acelerar a aplicação. Também é melhor apenas ler-se o fluxo de entrada em uma matriz ou string e, em seguida, criar fluxos de entrada para cada uso?
EDIT 2:
Infelizmente para reabrir a questão, mas as condições são um pouco diferentes quando se trabalha área de trabalho dentro e aplicação web. Primeiro de tudo, o InputStream i começar a partir do org.apache.commons.fileupload.FileItem em meu aplicativo tomcat web não suporta marcas, portanto, não pode repor.
Em segundo lugar, eu gostaria de ser capaz de manter o arquivo na memória para acesso mais rápido e menos problemas de io quando se lida com arquivos.
Solução
Você pode decorar InputStream sendo passado para POIFSFileSystem com uma versão que, quando close () é chamada, ela responde com 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();
}
}
testcase
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
Você pode ler o arquivo inteiro em um byte [] (modo gole), em seguida, passá-lo para um ByteArrayInputStream
Outras dicas
Tente BufferedInputStream, que adiciona marca e funcionalidade de redefinição para outro fluxo de entrada, e apenas substituir seu método 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();
}
}
Assim:
UnclosableBufferedInputStream bis = new UnclosableBufferedInputStream (inputStream);
e uso bis
onde quer inputStream foi usado antes.
Isso funciona corretamente:
byte[] bytes = getBytes(inputStream);
POIFSFileSystem fileSystem = new POIFSFileSystem(new ByteArrayInputStream(bytes));
onde getBytes é assim:
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();
}
Use abaixo implementação para mais uso personalizado -
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();
}
}
}
O que exatamente você quer dizer com "cache"? Você quer o diferente POIFSFileSystem para começar no início do fluxo? Se assim for, não há absolutamente nenhum ponto cache nada em seu código Java; isso será feito pelo sistema operacional, basta abrir um novo fluxo.
Ou você wan para continuar a leitura no ponto onde o primeiro POIFSFileSystem parou? Isso não é cache, e é muito difícil de fazer. A única maneira que eu posso pensar se você não pode evitar o fluxo de ficar fechada seria escrever um wrapper fino que conta quantos bytes foram lidos e, em seguida, abrir um novo fluxo e ignorar que muitos bytes. Mas isso pode falhar quando POIFSFileSystem internamente usa algo como uma BufferedInputStream.
Se o arquivo não é tão grande, lê-lo em uma matriz byte[]
e dar POI um ByteArrayInputStream
criado a partir dessa matriz.
Se o arquivo é grande, então você deve se importa, uma vez que o sistema operacional irá fazer o caching para você da melhor forma que puder.
[editar] commons-io Use Apache para ler o arquivo em uma matriz de bytes em uma jeito eficiente. Não use int read()
uma vez que lê o byte arquivo byte que é muito lento!
Se você quiser fazê-lo sozinho, use um objeto File
para obter o comprimento, criar a matriz ea um loop que lê bytes do arquivo. Você deve circuito desde read(byte[], int offset, int len)
pode ler menos de bytes len
(e geralmente faz).
Isso é como eu iria implementado, para ser usado com segurança com qualquer InputStream:
- escrever o seu próprio invólucro InputStream onde você criar um arquivo temporário para espelhar o conteúdo fluxo original
- despejo tudo lidos do fluxo de entrada original para este arquivo temporário
- quando o fluxo foi lido completamente você vai ter todos os dados espelhados no arquivo temporário
- usar InputStream.reset para switch (inicializar) a corrente interna para um FileInputStream (mirrored_content_file)
- a partir de agora você vai perder a referência do fluxo original (pode ser coletado)
- adicionar uma nova versão () método que irá remover o arquivo temporário e liberar qualquer fluxo aberta.
- Você mesmo pode chamar release () de finalize para garantir que o arquivo temporário é liberação no caso de você esquecer de chamar release () (na maioria das vezes você deve evitar usar finalize , chamar um método para recursos objeto release). consulte Por que você nunca implementar 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));
}
Isso funciona. IOUtils faz parte dos Comuns IO.
Esta resposta itera sobre os anteriores 1 | 2 baseado no BufferInputStream
. As principais alterações são de que ele permite a reutilização infinito. E cuida de fechar o fluxo de entrada da fonte original para liberar recursos do sistema-up. Seu sistema operacional define um limite para essas e você não quer que o programa seja executado fora do identificadores de arquivo ( É também por isso que você deve sempre 'consumir' respostas por exemplo, com o apache EntityUtils.consumeQuietly()
). Editar Atualizado o código para manipular para os consumidores Gready que o uso read(buffer, offset, length)
, nesse caso, pode acontecer que BufferedInputStream
se esforça para olhar para a fonte, este código protege contra esse uso.
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();
}
}
}
Para reutilizar apenas fechá-lo primeiro se não fosse.
Uma limitação é que embora se o fluxo está fechado antes de todo o conteúdo do fluxo original tenha sido lido, então este decorador terá dados incompletos, então certifique-se todo o fluxo é lido antes de fechar.
Eu só adicionar a minha solução aqui, como isso funciona para mim. Basicamente, é uma combinação de topo duas respostas:)
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();
}