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.

Foi útil?

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();
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top