Qual a forma de ler uma inputstream com melhor desempenho?

7 respostas
C

Estou tentando ler uma input stream usando as classes BufferedReader e InputStreamReader, mas a leitura do arquivo texto de várias linhas está demorando cerca de 30 segundos, ou seja, tempo pra caramba.

Aqui está o método:

public String getHtml() throws IOException
    {
        /**Obtém o conteúdo da página HTML e o retorna como uma String*/
        long tempo = System.currentTimeMillis();
        URLConnection site = getConnection();
        BufferedReader reader = new BufferedReader( new InputStreamReader( site.getInputStream() ) );
        System.out.println("Tempo para baixar a URL: "+(System.currentTimeMillis()-tempo));
        String html = ""; 
        tempo = System.currentTimeMillis();
        String linha = "";
        while( ( linha = reader.readLine() ) != null )
        {
            html += linha;
        }
        System.out.println("Tempo para gerar o HTML: " + (System.currentTimeMillis()-tempo) );
        return html;
    }

Quais outras formas de leitura recomendam?

7 Respostas

C

Bom, alterei uma coisa no método, que foi substituir a concatenação da String por um objeto da classe StringBuffer, o que fez com que o tempo de processamento caisse de 30s para cerca de 8s. Mas ainda está alto para o que preciso. Não há como otimizar ainda mais?

Segue o código atual:

public String getHtml() throws IOException
    {
        /**Obtém o conteúdo da página HTML e o retorna como uma String*/
        long tempo = System.currentTimeMillis();
        URLConnection site = getConnection();
        BufferedReader reader = new BufferedReader( new InputStreamReader( site.getInputStream() ) );
        System.out.println("Tempo para baixar a URL: "+(System.currentTimeMillis()-tempo));
        StringBuffer html = new StringBuffer();
        tempo = System.currentTimeMillis();
        String linha = "";
        while( ( linha = reader.readLine() ) != null )
        {
            html.append(linha);
        }
        System.out.println("Tempo para gerar o HTML: " + (System.currentTimeMillis()-tempo) );
        return html.toString();
    }
T

Troque isto, que é realmente não-performático ("+=") por StringBuilder e append:

// Seu código lento
     String html = "";   
     tempo = System.currentTimeMillis();  
     String linha = "";  
     while( ( linha = reader.readLine() ) != null )  
     {  
         html += linha;  
     }
// Código turbinado
     StringBuilder sbHtml = new StringBuilder();
     String html;   
     tempo = System.currentTimeMillis();  
     String linha = "";  
     while( ( linha = reader.readLine() ) != null )  
     {  
         sbHtml.append (linha);  
     } 
     html = sbHtml.toString();
T

Você pode aumentar o tamanho do buffer do BufferedReader para um valor maior. Veja a documentação porque há um construtor que aceita um valor maior para o buffer. O default é 8KB, se não me engano; você pode aumentar e ver até que ponto melhora a leitura.

H

Thingol, só um comentário.
A partir da versão 5 (onde passa a existir o StringBuilder) ao que me consta,
a concatenação de strings é feita por baixo dos panos usando StringBuilder.
Dessa forma construções usando “+” são tão eficientes quanto aquelas que usam StringBuilder explicitamente.

Pode-se testar isso com o exemplo abaixo, debugando com step into.

String teste = "";
for (int i = 0; i < 10; i++) {
      teste+="x";
}

Não sei se isso acontece em todos os casos, e por favor me corrija caso eu esteja falando besteiras. :wink:

[]'s
Homero

C

Bom, estou usando a versão 6 aqui, e estava extremamente lento com o operador “+=”.

Tanto com StringBuffer quanto como StringBuilder o tempo de montagem da String está sendo o mesmo, ambos muito mais rápidos que o velho “+=”.

Está em cerca de 8s, lento ainda para o que preciso.

Tentei aumentar o tamanho do buffer do BufferedReader, mas não percebi mudança no tempo para processar o arquivo.

Tentei implementar a leitura da seguinte forma:

public String getHtml() throws IOException
    {
        /**Obtém o conteúdo da página HTML e o retorna como uma String*/
        long tempo = System.currentTimeMillis();
        URLConnection site = getConnection();
        BufferedInputStream reader = new BufferedInputStream( site.getInputStream(),);
        System.out.println("Tempo para baixar a URL: "+(System.currentTimeMillis()-tempo));
        StringBuilder html = new StringBuilder();
        tempo = System.currentTimeMillis();
        String linha = "";
        byte [] buffer = new byte[4096];
        int b;
        while( ( b = reader.read(buffer, 0, buffer.length) )!= -1 )
        {
            html.append(new String(buffer) );
        }
        System.out.println("Tempo para gerar o HTML: " + (System.currentTimeMillis()-tempo) );
        return html.toString();
    }

Mas o tempo continua na mesma faixa que estava com o BufferedReader, independentemente do tamanho do buffer.

T
String teste = "";
for (int i = 0; i &lt; 10; i++) {
      teste+="x";
}

O seu código é equivalente a:

String teste = "";
for (int i = 0; i &lt; 10; i++) {
      teste = (new StringBuilder (teste).append ("x")).toString();
}

Ou seja, cria-se um StringBuilder, copia-se o valor anterior do objeto "teste" para o stringBuilder, efetua-se o append, e então toString cria um novo objeto String a partir do StringBuilder. Isso é realmente lento, como você deve ter comprovado, porque é uma vez por iteração. Digamos que haja 1000 iterações; então serão criados 2000 objetos e efetuadas 1000 cópias desnecessárias (não levando em conta o caso em que o "append" pode acabar estourando o buffer corrente do StringBuilder e acabar recopiando o valor do char[] interno para um char[] maior).

O código padrão é:

String teste;
StringBuilder sbTeste = new StringBuilder();
for (int i = 0; i &lt; 10; i++) {
      sbTeste.append ("x");
}
teste = sbTeste.toString();

Isso cria apenas 2 objetos - um StringBuilder e um String no final (não estou levando em conta os objetos char[] que têm de ser criados à medida em que o StringBuilder é expandido), e nenhuma cópia desnecessária é efetuada.

T

Agora vou fazer uma pergunta: como o tal “site.getInputStream()” é feito a partir de uma URLConnection ou de um socket, pode ser que você esteja agora enfrentando problemas com banda, não com a velocidade da InputStream.

De qualquer maneira, use um buffer maior - 4KB ainda é pequeno e consegue ser menor que o tal buffer do BufferedReader.

Se você quer fazer um programa que faz download de arquivos grandes (tal como um download manager), você precisa aprender como é que se pode efetuar N conexões ao mesmo site, baixando a partir de pontos diferentes, e saber controlar isso.

Isso é o que todos os download managers (incluindo os do browser Firefox e o download manager da Microsoft - não, ele não vem com o browser - ) fazem.

Criado 1 de outubro de 2008
Ultima resposta 1 de out. de 2008
Respostas 7
Participantes 3