Qual a melhor maneira (mais rápida) de ler um arquivo de um servidor web? – java android web-service

Pergunta:


Eu preciso ler um arquivo em um servidor web, mas quando preciso armazenar o conteúdo em um array de bytes está demorando muito. Alguém sabe um modo mais rápido de se fazer isso? segue meu código. Agradeço desde já.

try {
        url = new URL(surl);

        urlConnection = (HttpURLConnection) url.openConnection();
        InputStream input = new BufferedInputStream(urlConnection.getInputStream());    
        int b = input.read();
        List<Byte> bytes = new LinkedList<Byte>();
        while (b != -1) {
            bytes.add((byte) b);
            b = in.read();
        }
        byte[] array = new byte[bytes.size()];


        //AQUI ESTÁ O PROBLEMA, ESTÁ DEMORANDO MUITO!
        for (int i = 0; i < bytes.size(); i++) {
            array[i] = bytes.get(i).byteValue();
        }


        String str = new String(array);
        myreturn = str;

    }

Autor da pergunta daniel12345smith

Victor Stafusa

Lendo arquivos de forma rápida

No Java, existem várias classes para leitura de arquivos, com e sem buffering, de acesso aleatório, thread-safe, e mapeamento de memória. Algumas destas são muito mais rápidas do que outras.

FileInputStream com leitura de byte

O FileInputStream abre um arquivo por nome ou pelo objeto File. O método read() lê byte após byte do arquivo.

FileInputStream usa sincronização para torná-lo thread-safe.

FileInputStream f = new FileInputStream(name);
int b;
long checkSum = 0L;
while ((b = f.read()) != -1) {
    checkSum += b;
}

FileInputStream com leitura de array de byte

O FileInputStream faz uma operação de I/O em cada leitura e ele sincroniza em todas as chamadas de método para torná-lo thread-safe. Para reduzir essa sobrecarga, pode-se ler vários bytes de uma vez em um array de buffer de bytes.

FileInputStream f = new FileInputStream(name);
byte[] barray = new byte[SIZE];
long checkSum = 0L;
int nRead;
while ((nRead = f.read(barray, 0, SIZE)) != -1)
    for (int i = 0; i < nRead; i++) {
        checkSum += barray[i];
    }
 }

BufferedInputStream com leitura de bytes

O BufferedInputStream lida com o FileInputStream fazendo o buffer por você. Ele faz o wrap da entrada de stream, cria um array de bytes interno (normalmente 8 KB), e o preenche para fazer a leitura. O método read() pega cada byte do buffer.

BufferedInputStream utiliza sincronização para ser thread-safe.

BufferedInputStream f = new BufferedInputStream(
    new FileInputStream(name));
int b;
long checkSum = 0L;
while ((b = f.read()) != -1) {
    checkSum += b;
}

BufferedInputStream com leitura de array de byte

BufferedInputStream sincroniza todos os métodos ao fazer chamadas thread-safe. Para reduzir a sincronização e overhead de chamadas ao método, faça menos chamadas ao método read() fazendo a leitura de múltiplos bytes de uma vez.

BufferedInputStream f = new BufferedInputStream(
    new FileInputStream(name));
byte[] barray = new byte[SIZE];
long checkSum = 0L;
int nRead;
while ((nRead = f.read(barray, 0, SIZE)) != -1) {
    for (int i = 0; i < nRead; i++) {
        checkSum += barray[i];
    }
}

RandomAccessFile com leitura de bytes

RandomAccessFile abre o arquivo por nome ou objeto File. Ele pode ler, escrever, ou ler e escrever pela posição que se escolher dentro do arquivo. O método read() lê o próximo byte da atual posição do arquivo.

RandomAccessFile é thread-safe.

RandomAccessFile f = new RandomAccessFile(name);
int b;
long checkSum = 0L;
while ((b = f.read()) != -1) {
    checkSum += b;
}

RandomAccessFile com leitura de array de bytes

Tal como FileInputStream, RandomAccessFile enfrenta o problema de efetuar um operação I/O em cada acesso e sincronização em todas as chamadas a métodos para ser thread-safe. Para reduzir esse gargalo, pode-se fazer menos chamadas a métodos passando os bytes para um array e lendo a partir do array.

RandomAccessFile f = new RandomAccessFile(name);
byte[] barray = new byte[SIZE];
long checkSum = 0L;
int nRead;
while ((nRead = f.read(barray, 0, SIZE)) != -1) {
    for (int i = 0; i < nRead; i++) {
        checkSum += barray[i];
    }
}

FileChannel com ByteBuffer e busca de bytes

FileInputStream e RandomAccessFile podem retornar um FileChannel para operações mais baixo nível com I/O. O método read() do FileChannel preenche um ByteBuffer criado utilizando o método allocate() da classe ByteBuffer. O método get() da classe ByteBuffer recupera o próximo byte do buffer.

FileChannel e ByteBuffer não são thread-safe.

FileInputStream f = new FileInputStream(name);
FileChannel ch = f.getChannel();
ByteBuffer bb = ByteBuffer.allocate(SIZE);
long checkSum = 0L;
int nRead;
while ((nRead = ch.read(bb)) != -1) {
    if (nRead == 0) {
        continue;
    }
    bb.position(0);
    bb.limit(nRead);
    while (bb.hasRemaining()) {
        checkSum += bb.get( );
     }
    bb.clear();
}

FileChannel com ByteBuffer e busca de array de bytes

Para reduzir o gargalo da chamada de métodos de um byte de cada vez, recupere um array de bytes por vez. O array e o ByteBuffer podem ter tamanhos diferentes.

FileInputStream f = new FileInputStream(name);
FileChannel ch = f.getChannel();
ByteBuffer bb = ByteBuffer.allocate(BIGSIZE);
byte[] barray = new byte[SIZE];
long checkSum = 0L;
int nRead, nGet;
while ((nRead = ch.read(bb)) != -1) {
    if (nRead == 0) {
        continue;
    }
    bb.position(0);
    bb.limit(nRead);
    while(bb.hasRemaining()) {
        nGet = Math.min(bb.remaining(), SIZE);
        bb.get(barray, 0, nGet);
        for (int i = 0; i < nGet; i++) {
            checkSum += barray[i];
        }
    }
    bb.clear( );
}

FileChannel com array de ByteBuffer e acesso a array de bytes

Um ByteBuffer criado com o método allocate() usa storage interno para guardar os bytes. Ao invés de utilizar essa estratégia, chame o método wrap() para fazer um wrap do ByteBuffer envolta do seu próprio array de bytes. Isso permite que o array seja acessado diretamente após cada leitura, reduzindo o gargalo pela chamada de método e cópia de dados.

FileInputStream f = new FileInputStream(name);
FileChannel ch = f.getChannel();
byte[] barray = new byte[SIZE];
ByteBuffer bb = ByteBuffer.wrap(barray);
long checkSum = 0L;
int nRead;
while ((nRead = ch.read(bb)) != -1) {
    for (int i = 0; i < nRead; i++) {
        checkSum += barray[i];
    }
    bb.clear();
}

FileChannel com alocação direta de ByteBuffer

Um ByteBuffer criado com o método allocateDirect() pode utilizar diretamente o storage na JVM ou no sistema operacional da máquina. Isso pode reduzir a cópia de dados para o array do seu aplicativo, evitando alguma sobrecarga.

FileInputStream f = new FileInputStream(name);
FileChannel ch = f.getChannel();
ByteBuffer bb = ByteBuffer.allocateDirect(SIZE);
long checkSum = 0L;
int nRead;
while ((nRead = ch.read(bb)) != -1) {
    bb.position(0);
    bb.limit(nRead);
    while (bb.hasRemaining()) {
        checkSum += bb.get( );
    }
    bb.clear();
}

FileChannel com alocação direta de ByteBuffer e busca por array de bytes

E claro, você pode recuperar arrays de bytes para reduzir a sobrecarga em chamada do método. O tamanho do buffer pode ser diferente do tamanho do array.

FileInputStream f = new FileInputStream(name);
FileChannel ch = f.getChannel();
ByteBuffer bb = ByteBuffer.allocateDirect(BIGSIZE);
byte[] barray = new byte[SIZE];
long checkSum = 0L;
int nRead, nGet;
while ((nRead = ch.read(bb)) != -1) {
    if (nRead == 0) {
        continue;
    }
    bb.position(0);
    bb.limit(nRead);
    while(bb.hasRemaining()) {
        nGet = Math.min(bb.remaining(), SIZE);
        bb.get(barray, 0, nGet);
        for (int i = 0; i < nGet; i++) {
            checkSum += barray[i];
        }
    }
    bb.clear();
}

FileChannel com MappedByteBuffer e recuperando com bytes

O método da classe FileChannel, map, pode retornar um MappedByteBuffer que guarda em memória parte ou todo o arquivo em espaço de memória da aplicação. Isso permite mais acesso direto ao arquivo sem um buffer intermediário. Chame o método get() da classe MappedByteBuffer para recuperar o próximo byte.

FileInputStream f = new FileInputStream(name);
FileChannel ch = f.getChannel();
MappedByteBuffer mb = ch.map(ch.MapMode.READ_ONLY,
    0L, ch.size());
long checkSum = 0L;
while (mb.hasRemaining()) {
    checkSum += mb.get();
}

FileChannel com MappedByteBuffer e leitura de array de bytes

E recuperar arrays de bytes para diminuir a sobrecarga ao método.

FileInputStream f = new FileInputStream(name);
FileChannel ch = f.getChannel();
MappedByteBuffer mb = ch.map(ch.MapMode.READ_ONLY,
    0L, ch.size());
byte[] barray = new byte[SIZE];
long checkSum = 0L;
int nGet;
while (mb.hasRemaining()) {
    nGet = Math.min(mb.remaining(), SIZE);
    mb.get(barray, 0, nGet);
    for (int i = 0; i < nGet; i++) {
        checkSum += barray[i];
    }
}

FileReader e BufferedReader

As duas classes leem caracteres ao invés de bytes. Por esse motivo precisam transformar os bytes em caracteres, levando mais tempo que qualquer uma das estratégias mostrada acima.

Mais rápido

Se formos escolher a estratégia mais rápida, seria uma dessas:

  • FileChannel com MappedByteBuffer e leitura de array de bytes.
  • FileChannel com alocação direta de ByteBuffer e busca por array de bytes.

TL;DR

A maneira mais rápida depende do objetivo do programa. Se a ideia é carregar tudo em memória, basta usar um método mais eficiente.

Lendo arquivo numa String

A maneira mais rápida que conheço de carregar um arquivo local para uma String em memória é tão simples quanto isso:

String conteudo = new String(Files.readAllBytes(Paths.get("meu.txt")));

No entanto, isso não funciona para arquivos remotos, acessados via HTTP.

Lendo URL numa String

Neste caso, o método mais rápido é continuar usando o InputStream e um método melhor para ler os bytes.

Conforme reportado em outros lugares, a forma mais eficiente é usando o método sun.misc.IOUtils.readFully(), assim:

    InputStream input = new URL("http://www.textfiles.com/humor/mel.txt").openStream();
    String conteudo = new String(IOUtils.readFully(input, -1, true));

Riscos e alternativas

Claro que usar uma implementação interna de um JDK proprietário nem sempre é uma boa ideia. O método pode mudar ou deixar de existir em alguma versão futura.

A boa notícia é que é fácil substituir por uma alternativa. Uma delas é a biblioteca Apache Commons IO, cujo método IOUtils.toString() também faz o trabalho num só passo:

String conteudo = IOUtils.toString(input, "UTF-8");

A biblioteca Google Guava também faz algo parecido no método ByteStreama.toByteArray():

String conteudo = new String(ByteStreams.toByteArray(input]));

No Java 9 não será necessário código adicional, pois a classe InputStream será provida com novos métodos para cópia de bytes em massa.

Considerações

Primeiro, não é preciso usar exatamente o método mais rápido, pois certamente o gargalo de desempenho acabará sendo o download do arquivo. Então, eu recomendaria usar uma biblioteca e não o método mais rápido que usa a biblioteca interna.

Segundo, a implementação atual está lenta porque está fazendo uso ineficiente dos recursos, lendo tudo numa lista e copiando tudo de novo num array e depois tudo de novo numa String. São pelo menos 3 vezes mais memória que o necessário.

Terceiro, muitas vezes não precisamos necessariamente carregar todo o arquivo em memória. Se logo após essa rotina você grava o conteúdo num arquivo, seria mais eficiente fazer a leitura e gravação ao mesmo tempo. Uma forma bem simples é usando a rotina IOUtils.copy da biblioteca Apache.

E tome cuidado com imports, pois várias bibliotecas tem classes chamadas IOUtils. Só neste exemplo vimos duas.

Fonte

Related Posts:

Qual a diferença entre AppCompatActivity e Activity? – android android-activity
Pergunta: Qual a diferença da AppCompatActivity para Activity ? A partir de qual versão a AppCompatActivity foi adicionada ao Android? Autor da pergunta Luhhh A diferença reside ...
Como abreviar palavras em PHP? – php string
Pergunta: Possuo informações comuns como nome de pessoas e endereços, e preciso que elas contenham no máximo 30 caracteres sem cortar palavras. Exemplo: 'Avenida Natalino João Brescansin' ...
Qual é a finalidade de um parêntese vazio numa declaração Lambda? – c# expressões-lambda característica-linguagem
Pergunta: Criei um exemplo de uma declaração Lambda sem argumentos, entretanto, estou com duvidas referente a omissão do parêntese vazio () na declaração. Veja o exemplo: class ...
Boas práticas para URI em API RESTful – api rest restful
Pergunta: Estou com dúvida em relação às URIs de alguns recursos da api que estou desenvolvendo. Tenho os recursos projetos e atividades com relação 1-N, ...
Dúvidas sobre a integração do MySQL com Java – java mysql netbeans
Pergunta: Estou criando um sistema no NetBeans, utilizando a linguagem Java e o banco de dados MySQL. Escrevi o seguinte código para realizar a conexão ...
Qual é a finalidade da pasta Model do framework Inphinit? – php inphinit
Pergunta: No Inphinit micro-framework existe a pasta Model que fica dentro da pasta application, e nela é onde ficam as classes, mas eu estou muito ...
Uso do ‘@’ em variáveis – javascript typescript coffeescript
Pergunta: Vejo em algumas linguagens que compilam para javascript, como TypeScript e CoffeeScript, o uso do @ em variáveis, como também, casos em que o ...
Qual tamanho máximo um arquivo JSON pode ter? – json arquivo
Pergunta: Vou dar um exemplo para conseguir explicar minha duvida: Preciso recuperar informação de imagens vindas de uma API, esse banco de imagens me retorna JSON's ...
O que é Teste de Regressão? – terminologia engenharia-de-software testes
Pergunta: Na matéria de Teste de Software o professor abordou um termo chamado Teste de Regressão, isto dentro da disciplina de teste de software. Sendo ...
O que é um construtor da linguagem? – php característica-linguagem
Pergunta: Em PHP, já li e ouvi várias vezes a respeito dos Construtores da Linguagem. Os casos que sempre ouvi falar deles foi em casos ...
Função intrínseca para converter numérico para string – cobol
Pergunta: Estou a tentar saber se existe alguma função intrínseca do COBOL para converter um data numérico para string sem precisar usar a cláusula REDEFINES: ( ...
Porque usar implements? – java android
Pergunta: Qual a diferença entre usar btn.setOnClickListener(new OnClickListener() { e public class MainActivity extends Activity implements OnClickListener{ Estive fazendo um curso de Android e meu professor falou que ...
O que é XHTML e quando deve ser usado? – html xml xhtml
Pergunta: O que eu sei é que o XHTML precisa ser XML válido. Isso implica, por exemplo, que todas as tags precisam ser fechadas. Por ...
Uma placa aceleradora de vídeo pode melhorar o desempenho não-gráfico? [fechada] – desempenho
Pergunta: Para desenvolver em Ruby on Rails, eu utilizo aqui uma máquina virtual do VirtualBox com Ubuntu Server 14.04 sem interface gráfica instalada. Recentemente descobri uma ...
Concat() VS Union() – c# .net
Pergunta: Qual a diferença entre Concat() e Union() ? Quando usar Concat() e quando usar Union() ? Somente pode ser usado em list ? ...

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *